что такое корнер кейс
corner case
Смотреть что такое «corner case» в других словарях:
Corner case — A corner case (or pathological case) is a problem or situation that occurs only outside of normal operating parameters – specifically one that manifests itself when multiple environmental variables or conditions are simultaneously at extreme… … Wikipedia
Corner Gas — The Corner Gas logo Format Comedy Created by Brent Butt Starring Brent Butt … Wikipedia
Corner Boys — The Wire episode Episode no. Season 4 Episode 8 … Wikipedia
Case Vacanze Caputo — (Montelepre,Италия) Категория отеля: Адрес: Via Madonna Del Carmine 43, 90040 Montelepre … Каталог отелей
Corner solution — A corner solution is a special solution to an agent s maximization problem in which the quantity of one of the arguments in the maximized function is zero. The more usual solution will lie in the non zero interior at the point of tangency between … Wikipedia
Corner transfer matrix — In statistical mechanics, the corner transfer matrix describes the effect of adding a quadrant to a lattice. Introduced by Rodney Baxter in 1968 as an extension of the Kramers Wannier row to row transfer matrix, it provides a powerful method of… … Wikipedia
Corner detection — Feature detection Output of a typical corner detection algorithm … Wikipedia
Corner kick — A picture of the exact moment the blue white team s corner kick is taken. A corner kick is a method of restarting play in a game of association football. It was first devised in Sheffield under the Sheffield Rules 1867. It was adopted by the… … Wikipedia
Corner — For other uses, see Corner (disambiguation). A corner is the place where two lines meet at an angle, and a concave corner of intersecting walls is generally thought to be the least beneficial position to be in a life or death situation. From this … Wikipedia
Corner — Recorded in the spellings of Corner and Cornner, this interesting name is English Medieval but from two quite distinct French origins. The first is from the word cornier meaning an angle or corner, and as such was introduced into England after… … Surnames reference
Case’s Corner Historic District — Infobox nrhp | name =Case s Corner Historic District nrhp type = hd caption = location= Weston, Massachusetts lat degrees = 42 lat minutes = 21 lat seconds = 29 lat direction = N long degrees = 71 long minutes = 17 long seconds = 55 long… … Wikipedia
Тестирование глазами разработчика: инструменты, мифы, ситуации
Евгений Сафронов, Senior Developer, DataArt
«Тестирование можно использовать для того, чтобы доказать наличие ошибок в программе, и никогда — для того чтобы доказать их отсутствие!»
Эдсгер Дейкстра
Тестирование — это прикладная, стандартизированная, инженерная практика, которая применима в большинстве отраслей человеческой жизни. Тестирование, как философия, метрика или практика, существует намного дольше, чем программирование. К примеру, мы выковали меч. Чтобы проверить достаточно ли острым он получился, его испытывают. В некоторые эпохи даже на живом человеке, скажем, рабе.
Тестирование — это проверка работоспособности программы, предмета или любой промышленной разработки. Как и в любом деле, здесь есть свои тонкости и своя философия. Она, наверное, ближе тестировщикам, которые на произведенные нами вещи смотрят деструктивно — они с самого начала думают о том, как сломать предложенный разработчиками продукт. Это не очень типично для пользователей, которые более предсказуемы и обычно находят ошибки, случайно пытаясь сделать с нашей программой что-то нетипичное. У разработчиков подход к программам в принципе другой, но мы должны помнить: тестировщики должны ломать то, что мы создали — это их хлеб.
Почему тестировать программы так важно?
Разработчики не мыслят через тестирование, мы мыслим созидательно — и это может быть источником проблем. Когда нас просят написать программу, мы прежде всего думаем над концептами, структурами данных, их описанием и взаимодействием. В результате мы представляем решение — готовое, пусть и переполненное багами. Обычно мы слабо представляем, что будет, если изменятся входные данные, если пользователь будет в большом количестве совершать нетипичные операции.
При этом практика показывает, самые дорогостоящие ошибки случаются в результате небольших изменений. Потому что, если вы плохо написали кусок кода, более опытный коллега попросит вас его переделать. Обладая опытом, вы сами будете замечать неудачные фрагменты практически сразу. Но когда ошибки минимальны — отдельный символ, точка, знак финансовой операции, ошибка округления — найти их очень сложно, причем на любом этапе.
Рис. 1. Форма авторизации и количество вариантов ее заполнения.
Давайте посмотрим, почему возникает такая сложность в тестировании. На рисунке мы видим простую форму из трех полей. В первых двух полях можно ввести от 1 до 255 букв, в третьем от 1 до 20 символов. Можно также оставить строки пустыми. Внизу мы видим число возможных комбинаций, заметно превышающее количество элементарных частиц во вселенной. Я думаю, это убедительное доказательство того, что проверить все возможные кейсы нереально. Да и пытаться это сделать, наверное, нецелесообразно.
Типы ошибок в проектах
Рис. 2. Схема распределения ошибок по типам, согласно данным книги Стива Макконнелла «Совершенный код».
Примерно 25% общего объема приходится на структурные ошибки. Они возникают еще на этапе проектирования, когда вы создаете структуры данных и пишете реализации манипуляций с ними, то есть создаете некий «клей», скрепляющий эти структуры. Это огромный пласт фундаментальных ошибок.
Далее идут ошибки данных, связанные с реализацией функциональности и т. д. Интересно, что архитектура находится на последнем месте. Это можно объяснить тем, что, создав неправильную архитектуру, очень сложно в принципе выпустить конечный продукт.
Рис. 3. Распределение ошибок по стадиям разработки. Схема Стива Макконнелла.
На этап конструирования и проектирования приходится основной пласт ошибок и дефектов. Здесь работает известное правило Парето: 80 % дефектов локализованы лишь в 20 % вашего кода. Как правило, это какие-то корнер-кейсы. Если вы пишете операции с математической, например, с финансовой логикой, очень много ошибок может заключаться в пределах пограничных значений, при округлении чисел и т. п. Большинство ваших кейсов будут работать, но основная часть дефектов будет локализована в небольшом участке кода.
Вне зависимости от того, применяете ли вы ручное или юнит-тестирование, нельзя утверждать, что покрыв тестами 50 % кода, вам удалось предотвратить 50 % возможных дефектов. Если вы написали 100 юнит-тестов нельзя сказать, что итоговое качество вашего продукта повысилось, например, вдвое. Потому что разработчик, который не мыслит через призму тестирования, проверяет самые легкие кейсы. Нужные формы заполняются стандартными именем, фамилией и логином — разработчик идет дальше. Он с большой долей вероятности не станет проверять случаи, когда одно из полей остается пустым, или когда в него действительно вбили имя длиной в 255 знаков. Не будет он экспериментировать с нестандартными символами, комбинациями строчных и заглавных букв.
Тестировать продукт могут все: разработчик, тестировщик, менеджер, заказчик. Иногда в небольших проектах роль тестировщика может выполнять менеджер или разработчик. Но все же все четыре роли здесь очень важны — каждый участник процессе смотрит на объект со своей точки зрения. Разработчик — через призму того, как работает код. У него с самого начала есть большая часть информации о том, как сломать интерфейс. Для тестировщика найти ошибки и сломать продукт — прямая задача. Заказчик смотрит на тесты совершенно иначе: ему интересно, как написать меньше тестов и заплатить меньше денег, все равно получив в итоге качественный продукт. Но самая интересная роль у менеджера, который обычно может опираться на случаи из собственной практики. Впрочем, как правило, он служит мостом между разработчиками, тестировщиками и заказчиком, а его основная задача сводится к презентации продукта. Но именно менеджер отвечает за качество продукта на выходе.
Тестирование черного и белого ящиков
Классификация тестирования в виде анализа белого, серого и черного ящиков, интересна нам тем, что разработчик смотрит на собственный код как на белый ящик. А тестировщик и заказчик рассматривают программу как черный ящик — они до конца не понимают, как именно работает программа, знают только, какие фичи важны и должны работать обязательно.
Баг на сайте интернет-магазина, из-за которого какой-то товар не попадает в корзину, для разработчика — минорная проблема. Достаточно что-то закоммитить и раскоммитить, поменять переменные в одном месте — и все отлично заработает. Для тестировщика и тем более заказчика такой баг оказывается критическим, поскольку не позволяет клиенту купить нужный товар.
Но самый интересный ящик — серый. С ним работают те, кого я называю умными тестировщиамки, которые глубоко вникают в код и то, как работает продукт. Их интересует, как происходит деплой и коммуникация с базами данных.
Какие тесты пишут разработчики?
Рис. 4. Пирамида модульного тестирования.
Основа пирамиды — юнит-тестирование — желательно, чтобы юнит-тестов в проекте было много. Далее следуют интеграционное тестирование наших модулей, Acceptance Tests и непосредственное UI-тестирование конкретных фич.
F.I.R.S.T
F.I.R.S.T. — методология описания требованиий, которым должны отвечать тесты. Прежде всего, модульные, но в принципе эти характеристики можно экстраполировать и на автоматические тесты. Ее создатель — известный дядюшка Боб — автор многих практик программирования.
Рис. 5. Схема TDD-процесса.
Две базовых методологии юнит-тестирования — похожие концепты TDD и BDD. BDD-подход основывается на TDD и призван устранить небольшие недостатки, которые присутствуют в TDD. Синтаксис BDD-тестов в большей степени ориентирован на бизнес и понятен заказчику и вообще технически менее подкованному человеку. TDD — о том, как делать вещи правильно. BDD — о том, как делать правильные вещи.
Как выглядит TDD-процесс?
Первым делом вы пишете тест для кода, который еще не написан. Соответственно, видите, что он не работает. Чтобы он заработал, код ещё нужно написать. Потом вы все-таки пишете код, запускаете тест и убеждаетесь в том, что тест работает. Далее вы модифицируете свой тест (уточняете требования, добавлете проверку граничных условий и т. п.). После этого ваш тест перестает работать, и вы снова приходите к необходимости модифицировать ваш код. Этот процесс зациклен до того момента, пока ваш тест не будет совершенно четко отвечать конечным критериям и соответствовать задачам, которые перед вами стоят. Предположим, если вы решаете квадратное уравнение, ваша задача — написать функцию, которая находит корни квадратного уравнения. Предположим, вы написали тест, и он хорошо работает на базе действительных чисел. Но если пользователь захочет найти комплексное решение, то ничего не получится, тест завалится и потребует модификации. Поэтому эта схема должна быть в крови у разработчиков: нужно действовать по ней, пока ваша фича не будет соответствовать всем требованиям.
Как выглядит TDD?
Пример написан на JavaScript, но думаю, в синтаксическом плане он будет примерно также выглядеть для PHP или Java. Все достаточно очевидно. Открыв тест, можно легко сказать, какие моменты протестированы не были. Можно легко и быстро что-то добавить. И таким образом проверить, корректно ли работает ли ваша функция.
BDD очень похож на TDD, но отличается тем, что формулировки в описании немного лучше адаптированы для людей, менее плотно связанных с разработкой: менеджеров или бизнес-аналитиков.
Особенности модульного тестирования
Рис. 6. Инструменты юнит-тестирования.
QUnit — библиотека от разработчиков jQuery, позволяющая писать юнит-тесты в TDD-стиле, с механизмом assert. Вы пишете qunit.test, название теста, и что вы хотите протестировать.Затем запускаете его в отдельном файле, который должен видеть ваш код, и можете убедиться в том, что код работает.
Mocha — фреймворк для тестирования, позволяющий писать тесты в TDD и BDD-формате. Как правило, он используется совместно с другими инструментами для того, чтобы полностью реализовать TDD-подход в работе. То есть он позволяет запускать и описывать тесты в нужном формате, а к примеру за обработку проверок утверждений (asserts) отвечает другая библиотека (чаще всего Chai).
Sinon— инструмент для создания Mocks, stubs и spies, который очень часто используется в современных успешных проектах. Он содержит набор функций и модулей, которые здорово помогают и решают большое количество типичных проблем, которые возникают у разработчика во время создания юнит-тестов.
Jasmine — популярный BDD-библиотека, которая фактически стала стандартом в экосистеме самого распространенного Javascript-фреймворка Angular.
Navigation Component-дзюцу, vol. 3 — Corner-кейсы
В этой части трилогии про Navigation Component разберем как организовать навигацию в многомодульных приложениях, как работается с диплинками, а также рассмотрим кейсы со встраиваемыми фрагментами и диалогами.
Это третья и заключительная статья в цикле про различные кейсы навигации с Navigation Component-ом. Вы также можете ознакомиться с первой и второй частями
UPDATE 30.12.2020:
Выводы по поводу крашей диплинков, сделанные в этой статье, некорректны, так как при настройке BottomNavigationView из первой статьи была допущена серьёзная ошибка, которая и привела к множеству крашей. Подробности — в последней статье цикла.
Навигация в многомодульных приложениях
Если вы работаете с большим приложением, вероятно, вы уже разбили его на модули. Неважно, как именно. Может быть, вы создаёте отдельные модули для логики и UI, а может храните всю логику фичи (от взаимодействия с API до логики presentation-слоя) в одном модуле. Главное – у вас могут быть кейсы, когда требуется осуществить навигацию между двумя независимыми модулями.
Существует три способа как это сделать, разберём их один за другим.
App-модуль + интерфейсы
Первый способ – использовать ваш application-модуль в качестве хранилища всего графа навигации и определить в feature-модулях специальные интерфейсы для роутинга.
Структура приложения будет стандартной: есть app-модуль, который знает обо всех feature-модулях, есть feature-модули, которые не знают друг о друге. В этом способе ваши feature-модули пребывают в священном неведении о Navigation Component, и для навигации они будут определять интерфейсы примерно вот такого вида:
А ваш app-модуль будет реализовывать эти интерфейсы, потому что он знает обо всех action-ах и навигации:
Плюс этого способа – отсутствие дополнительных модулей, которые могут повлиять на скорость сборки приложения. Однако в минусах вы получаете:
Сомнительный, в общем, способ.
Графы навигации в feature-модулях + диплинки
Второй способ – вынести отдельные графы навигации в feature-модули и использовать поддержку навигации по диплинкам (она же – навигация по URI, которую добавили в Navigation Component 2.1).
Структура приложения будет ровно такой же, как и в первом способе: есть app-модуль, который зависит от всех feature-модулей, и есть feature-модули, которые друг о друге ничего не знают.
Но теперь ваш app-модуль не обязан содержать весь граф навигации приложения, он может содержать только его часть. А остальные кусочки будут содержать именно feature-модули.
После этого мы можем использовать этот диплинк для открытия экрана CompanyFragment из модуля :vacancy :
P.S. Есть, конечно, вариант сериализовать ваши сложные структуры в JSON и передавать их в качестве String-аргументов в диплинк, но это как-то… Странно.
Общий модуль со всем графом навигации
Третий способ – ввести для хранения всего графа навигации специальный модуль, который будет подключаться к каждому feature-модулю.
В чём соль? Несмотря на то, что common-модуль не знает о реализациях ваших destination-ов (фрагментах, диалогах, activity), он всё равно способен объявить граф навигации в XML-файлах! Да, Android Studio начинает сходить с ума: все имена классов в XML-е горят красным, но, несмотря на это, все нужные классы генерируются, Safe Args плагин работает как нужно. И так как ваши feature-модули подключают к себе common-модуль, они могут свободно использовать все сгенерированные классы и пользоваться любыми action-ами вашего графа навигации.
Плюс этого способа – наконец-то можно пользоваться всеми возможностями Navigation Component-а в любом feature-модуле. Из минусов:
Выводы по навигации в многомодульных приложениях
Работа с диплинками
Практически каждое большое приложение должно уметь поддерживать диплинки. И практически каждый Android-разработчик мечтал о простом способе работы с этими «глубокими ссылками». Окей, я мечтал. И казалось, что Navigation Component – ровно то, что нужно.
По моей задумке кейсы с диплинками должны были занимать довольно большую часть переходов тестового приложения.
У меня было три кейса с диплинками, которые я хотел реализовать с помощью Navigation Component.
Допустим, я хочу через диплинк открыть вкладку Favorites нижней навигации на главном экране после Splash-экрана:
Пусть я хочу открыть определённую вкладку ViewPager-а внутри вкладки Responses:
Пусть вкладка Profile теперь требует авторизации. И с помощью диплинка я хочу сначала показать Splash-экран, затем открыть флоу авторизации, а когда пользователь его пройдёт – открыть вкладку Profile.
Сразу скажу, что у меня не получилось нормально реализовать ни один из этих кейсов. И работа с диплинками в Navigation Component стала для меня самым большим разочарованием.
Диплинк для определённой вкладки нижней навигации
Прежде чем делать диплинк для второй вкладки, я решил настроить ссылку хотя бы для первой. Я решил, что для этого будет достаточно просто добавить диплинк на экран контейнера нижней навигации (не самой вкладки, а контейнера):
Затем я, следуя документации, добавил граф навигации с диплинком в Android Manifest:
А потом решил проверить, работает ли то, что я настроил при помощи простой adb-команды:
И-и-и… нет. Ничего не завелось. Я получил краш приложения с уже знакомым исключением – IllegalStateException: FragmentManager is already executing transactions. Дебаггер указывал на код, связанный с настройкой нижней навигации, поэтому я решил просто обернуть эту настройку в очередной Handler.post:
Это исправило краш, но приложение всё равно работало неправильно: запустив диплинк, мы пропустили Splash-экран, он просто не запускался. А это означает, что не отрабатывал код, который отвечал за инициализацию моего приложения.
Тогда я решил перенести диплинк в другой граф – внутрь вкладки нижней навигации.
И, запустив приложение, я получил ЭТО:
На гифке видно, как приложение запустилось, и мы увидели Splash-экран. После этого на мгновение показался экран с нижней навигацией, а затем приложение словно запустилось заново! Мы снова увидели Splash-экран, и только после его повторного прохождения появилась нужная вкладка нижней навигации.
И что самое неприятное во всей этой истории – это не баг, а фича.
Если почитать внимательно документацию про работу с диплинками в Navigation Component, можно найти следующий кусочек:
When a user opens your app via an explicit deep link, the task back stack is cleared and replaced with the deep link destination.
То есть наш back stack специально очищается, чтобы Navigation Component-у было удобнее работать с диплинками. Говорят, что когда-то давно, в бета-версии библиотеки всё работало адекватнее.
Мы можем это исправить. Корень проблемы – в методе handleDeepLink NavController-а:
Чтобы переопределить это поведение, нам потребуется:
Несложно, правда? Но кажется, что это уже слишком. Подобные исправления заставляют нас в будущем тратить всё больше и больше времени на поддержку нашего кастомного решения, ведь нужно будет обновлять свой фикс при каждом обновлении библиотеки. Около года назад коллеги из Яндекса столкнулись с похожей проблемой, и им пришлось делать примерно то же самое. Сочувствую.
В этот невесёлый момент я заметил ещё один баг, который был добавлен при попытке исправить краш с диплинками: сломалась обратная навигация из auth-флоу.
На гифке видно, как мы переключаемся на вкладку Profile, затем переходим во флоу авторизации. Потом мы нажимаем на кнопку Back и получаем просто белый экран. Если переключиться на какую-нибудь другую вкладку и обратно, то мы снова увидим контент вкладки Profile.
Для этой проблемы я уже даже не стал искать решение, потому что чертовски устал от этих войн за каждый кейс навигации. Если знаете как исправить, отпишитесь в комментариях, пожалуйста.
Диплинк на экран ViewPager-а внутри вкладки нижней навигации
Если у вас будет свой собственный NavController, корректно обрабатывающий диплинки, реализовать этот кейс будет просто.
Диплинк на экран, требующий авторизацию
Navigation Component не поддерживает диплинки с условием из коробки. Если вы хотите поддержать такое поведение, Google предлагает действовать следующим образом:
Возможности глобально решить мою задачу средствами Navigation Component-а я не нашёл.
Выводы по работе с диплинками в Navigation Component
Бонус-секция – кейсы БЕЗ проблем
Чтобы разбавить негатив от предыдущей секции, я покажу пару кейсов, с которыми вообще не было проблем. А я их ждал. Но их не было.
Навигация с экрана в экран
Допустим, у вас есть экран вакансий, с которого вы можете перейти на другую вакансию.
По опыту предыдущих кейсов я ожидал проблем и здесь, но всё оказалось достаточно просто – я определил навигацию с экрана на экран:
И этого оказалось достаточно – новый экран открывался поверх старого, при нажатии на кнопку Back навигация была корректной. Если бы я захотел, чтобы каждый новый экран открывался вместо текущего, было бы достаточно добавить атрибут popUpTo к моему action-у.
Навигация из встраиваемых фрагментов
В приложении hh есть специальный фрагмент для управления поисковыми выдачами. В нём мы инкапсулировали всю логику, связанную с отображением списка вакансий, чтобы можно было встраивать этот фрагмент в другие экраны. Пусть я задумал добавить подобный фрагмент на несколько вкладок моей нижней навигации так, чтобы при нажатии на элемент списка внутри этого фрагмента у меня открывался соответствующий экран.
Я добавил контейнер для будущего фрагмента со списком в вёрстку вкладки нижней навигации:
А затем в runtime-е добавил нужный мне фрагмент в этот контейнер:
Метод attachFragmentInfo на childFragmentManager – это extension-метод, который просто оборачивает всю работу с транзакциями, не более того.
А вот как я создал фрагмент:
Вся соль в пробросе реализации интерфейса для навигации – я инициализирую нужный мне фрагмент правильным навигатором, который будет срабатывать при нажатии на элемент списка.
Навигация между диалогами
Пусть у меня есть несколько BottomSheetDialog-ов, между которыми я хочу перемещаться с помощью Navigation Component.
Год назад с таким кейсом были какие-то проблемы, но сейчас всё работает как надо. Можно легко объявить какой-то dialog в качестве destination-а в вашем графе навигации, можно добавить action для открытия диалога из другого диалога.
Диалоги создавались как нужно, закрывались вовремя, навигация по кнопке Back отрабатывала как ожидалось.
Выводы по бонус-секции
Кейсы без проблем – существуют.
Подведём итоги
На данный момент нет никакой причины переводить большое приложение на Navigation Component. Слишком много проблем, слишком много костылей, постоянно нужно выдумывать что-то для осуществления не самых сложных кейсов навигации. Сам факт, что я ухитрился написать так много текста про проблемы с Navigation Component-ом, что-то да говорит.
Если у вас маленькое приложение, надо внимательно посмотреть на то, какие кейсы навигации вы хотите поддержать. Если задумываетесь про нижнюю навигацию, про диплинки – пожалуй, лучше реализовать всё по-старинке: либо руками, либо на Cicerone. В остальных случаях, если вас не пугает необходимость постоянно искать фиксы, можно воспользоваться Navigation Component-ом.