Русский
Русский
English
Статистика
Реклама

Автоматическое тестирование

Аспекты хороших юнит-тестов

02.05.2021 12:15:03 | Автор: admin

Эта статья является конспектом книги Принципы юнит-тестирования.

Давайте для начала перечислим свойства хороших юнит-тестов.

Первое. Интегрированы в цикл разработки. Пользу приносят только те тесты, которые вы активно используете; иначе писать их нет смысла.

Второе. Тестируют только самые важные части вашего кода. Не весь рабочий кодзаслуживает одинакового внимания.

Третье. Дают максимальную защиту от багов с минимальными затратами на сопровождение. Для этого нужно уметь распознавать эффективные тесты и писать их.

Однако распознавание и написание эффективного теста два разных навыка. И для приобретения второго навыка необходимо сначала освоить первый. Далее в этой статье будет показано, как распознать эффективный тест. Также будет рассмотрена пирамида тестирования и тестирование по принципу черного ящика / белого ящика.

Четыре аспекта хороших юнит-тестов

Хороший юнит-тест должен обладать следующими атрибутами: защита от багов, устойчивость к рефакторингу, быстрая обратная связь и простота поддержки.

Эти четыре атрибута фундаментальны. Они могут использоваться для анализалюбых автоматизированных тестов, будь то юнит-, интеграционные или сквозные(end-to-end) тесты.

Начнем с первого атрибута хорошего юнит-теста: защиты от багов. Баг (или регрессия) это программная ошибка. Как правило, такиеошибки возникают после внесения изменений в код.

Чем больше функциональности, тем выше вероятность того, что внесете баг в новую версию. Вот почему так важно разработать хорошую защиту отбагов. Без такой защиты будет невозможно или очень сложно обеспечить рост проекта в долгосрочнойперспективе из-за постоянно увеличивающегося количества ошибок.

Для оценки того, насколько хорошо тест проявляет себя в отношении защиты отбагов, необходимо принять во внимание, следующее:

  • объем кода, выполняемого тестом;

  • сложность этого кода;

  • важность этого кода с точки зрения бизнес-логики.

Как правило, чем больше кода тест выполняет, тем выше вероятность выявить в нембаг. Само собой, тест также должен иметь актуальныйнабор проверок (assertions).

Важен не только объем кода, но и его сложность и важность с точки зрения бизнес-логики. Код, содержащий сложную бизнес-логику, важнее инфраструктурного кода ошибки в критичной для бизнеса функциональности наносят наибольший ущерб.

Как следствие, тестирование тривиального кода обычно не имеет смысла. Этот кодслишком простой и не содержит сколько-нибудь значительного объема бизнес-логики.

Второй атрибут хорошего юнит-теста устойчивость к рефакторингу. Эта устойчивость определяет, насколько хорошо тест может пережить рефакторинг тестируемогоим кода без выдачи ошибок.

Рефакторингом называется модификация существующего кода без изменения его наблюдаемого поведения. Обычно рефакторинг проводится для улучшения нефункциональныххарактеристик кода: читаемости и простоты. Примеры рефакторинга переименованиеметода или выделение фрагмента кода в новый класс.

Рефакторинг может привести к ложному срабатыванию. Это ложный сигнал тревоги: тестпоказывает, что функциональность не работает, тогда как в действительности все работает как положено. Такие ложные срабатывания обычно происходят при рефакторинге кода, когда вы изменяете имплементацию, но оставляете поведение приложения без изменений. Чем меньше ложных срабатываний, тем лучше устойчивость к рефакторингу.

Почему столько внимания уделяется ложным срабатываниям? Потому что они могутиметь серьезные последствия для всего приложения. Цельююнит-тестирования является обеспечение устойчивого роста проекта. Устойчивыйрост становится возможным благодаря тому, что тесты позволяют добавлять новуюфункциональность и проводить регулярный рефакторинг без внесения ошибокв код.

Частые ложные срабатывания могут привести к следующим ситуациям:

  • Если тесты падают без веской причины, они притупляют вашу готовностьреагировать на проблемы в коде. Со временем вы привыкаете к таким сбоями перестаете обращать на них внимание. А это может привести к игнорированиюнастоящих ошибок.

  • Начинаете все меньшеи меньше доверять вашим тестам. Они уже не воспринимаются как что-то, начто вы можете положиться. Отсутствие доверия приводит к уменьшению рефакторинга, так как вы пытаетесь свести к минимуму потенциальные ошибки.

Что приводит к ложному срабатыванию?

Количество ложных срабатываний, выданных тестом, напрямую связано со структурой этого теста. Чем сильнее тест связан с деталями имплементации тестируемойсистемы, тем больше ложных срабатываний он порождает.Уменьшить количество ложных срабатываний можно только одним способом:отвязав тест от деталей имплементации тестируемой системы. Тест должен проверять конечный результат наблюдаемое поведение тестируемой системы, а недействия, которые она совершает для достижения этого результата.

Лучший вариант структурирования теста тот, при котором он рассказывает историю о предметной области. Если такой тест не проходит, это означает, что междуисторией и фактическим поведением приложения существует разрыв. Только такие падения тестов полезны они всегда несут полезную информацию о том, что пошлоне так.

Рис. 1 Тест слева связан с наблюдаемым поведением SUT, а не с деталями реализации. Такой тест более устойчив к рефакторингу, чем тест справаРис. 1 Тест слева связан с наблюдаемым поведением SUT, а не с деталями реализации. Такой тест более устойчив к рефакторингу, чем тест справа

Связь между первыми двумя атрибутами

Между первыми двумя аспектами хорошего юнит-теста(защита от багов и устойчивость к рефакторингу) существует связь. Оба атрибутавносят вклад в точность тестов, хотя и с противоположных позиций. Эти два атрибута также по-разному влияют на проект с течением времени: важно иметь хорошуюзащиту от багов сразу же после запуска проекта, но необходимость в устойчивостик рефакторингу возникает позднее.

Давайте рассмотрим более широкую картину того, что собой представляют результаты тестовых прогонов. Тесты могутпроходить или не проходить (строки таблицы), а сама функциональность можетработать либо правильно, либо неправильно (столбцы таблицы).

Ситуация, когда тест проходит, а тестируемая функциональность работает правильно, называется истинным отрицательным срабатыванием: тест правильно определяетсостояние системы (отсутствие в ней ошибок).

Рис. 2 - Отношение между защитой от багов и устойчивостью к рефакторингуРис. 2 - Отношение между защитой от багов и устойчивостью к рефакторингу

Если тест не выявляет ошибку, значит, возникла проблема. Ситуация соответствуетправому верхнему квадранту: ложноотрицательное срабатывание. И именно ее помогает избежать защита от багов. Тесты с хорошейзащитой от багов помогают минимизировать количество ложноотрицательныхсрабатываний ошибок II типа.

С другой стороны, существует симметричная ситуация: функциональность работаетправильно, но тест сообщает об ошибке. Это ложное срабатывание. И с ней помогает устойчивость к рефакторингу.

Количества ложных и ложноотрицательных срабатываний образуют метрику точности теста: чем меньше таких срабатываний, тем точнее тест. Сама метрика точности состоит из двух компонентов:

  • насколько хорошо тест выявляет присутствие ошибок (отсутствие ложноотрицательных срабатываний, сфера защиты от багов);

  • насколько хорошо тест выявляет отсутствие ошибок (отсутствие ложных срабатываний, сфера устойчивости к рефакторингу).

Как видно из формулы на рис. 3, улучшить точностьтеста можно двумя способами. Первый повышение числителя (сигнал), то естьповышение вероятности выявления ошибок. Второй уменьшение знаменателя(шум), то есть уменьшения вероятности ложных срабатываний.

Рис. 3 Формула точности тестаРис. 3 Формула точности теста

Третий и четвертый аспекты: быстрая обратнаясвязь и простота поддержки

Быстрая обратная связь является одним из важнейшихсвойств юнит-теста. Чем быстрее работают тесты, тем больше их можно включитьв проект и тем чаще вы их сможете запускать. В результате чего затраты на исправление этих ошибок уменьшаются почти донуля. С другой стороны, медленные тесты увеличивают время, в течение которогоошибки остаются необнаруженными, что приводит к увеличению затрат на ихисправление.

Простота поддержки оценивает затраты на сопровождение кода. Метрика состоит из двух компонентов:

  • Насколько сложно тест понять. Этот компонент связан с размером теста. Чемменьше кода в тесте, тем проще он читается и проще изменяется при необходимости.

  • Насколько сложно тест запустить. Если тест работает с внепроцессными зависимостями, вам придется тратить время на то, чтобы поддерживать эти зависимости в рабочем состоянии.

В поисках идеального теста

Произведение этих четырех атрибутов определяет эффективность теста. И в данномслучае автор книги использует термин произведение в математическом смысле: если одиниз атрибутов равен нулю, то ценность всего теста тоже обращается в нуль.

Установите достаточно высокий порог для минимальной требуемой эффективностии включайте в проект только те тесты, которые проходят этот порог. Небольшой набор высокоэффективных тестов намного лучше справится с задачей поддержанияроста проекта, чем большое количество посредственных тестов.

Возможно ли создать идеальный тест? К сожалению, получить максимальные оценки по всем четырем показателям невозможно. Дело в том, что первые три атрибута защита от багов, устойчивость к рефакторингу и быстрая обратная связь являютсявзаимоисключающими. Невозможно довести их до максимума одновременно: однимиз трех придется пожертвовать для максимизации двух остальных.

Нельзя просто обнулить один атрибут, чтобы сосредоточиться на остальных. Как упоминалось ранее, тест с нулевым значением в одной изчетырех категорий бесполезен. Следовательно, атрибуты нужно максимизироватьтак, чтобы ни один из них не падал слишком низко.

Первый пример сквозные (end-to-end) тесты. Сквозные тесты рассматривают систему с точки зрения конечного пользователя.Они обычно проходят через все компоненты системы, включая пользовательскийинтерфейс, базу данных и внешние приложения.

Так как сквозные тесты задействуют большой объем кода, они обеспечивают наилучшую защиту от багов. Сквозные тесты практически не выдают ложных срабатываний, а, следовательно,обладают хорошей устойчивостью к рефакторингу. Они не настаивают накакой-то конкретной имплементации, смотрят только на поведение приложения с точки зрения конечного пользователя.

Однако у сквозных тестов имеется крупный недостаток: они очень медленные. Любой проект, который полагается исключительнона такие тесты, не сможет получить быструю обратную связь. Именно поэтому невозможно обеспечить покрытие кода только сквозными тестами.

Второй пример максимизации двух из трех атрибутов за счет третьего тривиальныйтест. Такие тесты покрывают простой фрагмент кода, вероятность сбоя в которомневелика.

Рис. 4 - Тривиальный тест, покрывающий простой фрагмент кодаРис. 4 - Тривиальный тест, покрывающий простой фрагмент кода

В отличие от сквозных тестов, тривиальные тесты предоставляют быструю обратную связь. Кроме того, вероятность ложных срабатываний также мала, поэтомуони обладают хорошей устойчивостью к рефакторингу. Тем не менее тривиальныетесты вряд ли смогут выявить какие-либо ошибки, потому что покрываемый имикод слишком прост.

Третий пример - тест, который работает быстро и хорошо выявляет ошибки в коде, но делает это с большим количеством ложных срабатываний. Такие тесты называются хрупкими: они падают при любом рефакторингетестируемого кода независимо от того, изменилась тестируемая ими функциональность или нет.

Рис. 5 Места, которые занимают тесты по отношению друг к другуРис. 5 Места, которые занимают тесты по отношению друг к другу

Четвертый атрибут простота поддержки не так сильно связан с первыми тремя, за исключением сквозных (end-to-end) тестов. Сквозные тесты обычно имеют больший размер из-за необходимости подготовки всех зависимостей, к которымимогут обращаться такие тесты. Они также требуют дополнительных усилий дляподдержания этих зависимостей в работоспособном состоянии. Таким образом,сквозные тесты требуют больших затрат на сопровождение.

Выдержать баланс между атрибутами хорошего теста сложно. Тест не может иметьмаксимальных значений в каждой из первых трех категорий; также приходится учитывать аспект простоты поддержки. А значит, вам придется идти на компромиссы.Более того, на компромиссы придется идти так, чтобы ни один конкретный атрибутне оказался равным нулю. Уступки должны быть частичными и стратегическими.

По мнению автора книги, лучшие тесты демонстрируют максимально возможную простоту поддержки и устойчивостьк рефакторингу; всегда старайтесь максимизировать эти два атрибута. Компромисс сводится к выборумежду защитой от багов и быстротой обратной связи.

Рис. 6 Компромиссы между атрибутами хорошего тестаРис. 6 Компромиссы между атрибутами хорошего теста

Почему же устойчивость к рефакторингу не должна быть предметом для компромиссов?Потому что этот атрибут в основном сводится к бинарному выбору: тест либо устойчивк рефакторингу, либо нет. Между этими двумя состояниями почти нет промежуточныхступеней. А значит, пожертвовать небольшой частью устойчивости к рефакторингуне получится. С другой стороны, метрики защитыот багов и быстрой обратной связи более эластичны.

Компромисс между первыми тремя атрибутами хорошего юнит-теста напоминает теорему CAP. Эта теорема утверждает, что распределенное хранилище данных не можетпредоставить более двух из трех гарантий одновременно: согласованность (consistency) данных, доступность (availability), устойчивость к разделению (partition tolerance).

Сходство является двойным:

1. В CAP вы тоже можете выбрать максимум два атрибута из трех;

2. Устойчивость к разделению в крупномасштабных распределенных системахтакже не является предметом для компромиссов. Большое приложение такоекак, например, веб-сайт Amazon не может работать на одной машине. Вариантс достижением согласованности данных и доступности за счет устойчивостик разделению просто не рассматривается.

Пирамида тестирования

Концепция пирамиды тестирования предписывает определенное соотношениеразных типов тестов в проекте: юнит-тесты, интеграционные тесты, сквозные тесты.

Пирамида тестирования часто изображается состоящей из трех типов тестов. Ширина уровней пирамиды обозначает относительную долю тестов определенного типав проекте. Чем шире уровень, тем больше тестов. Высота уровня показывает, насколько близки эти тесты к эмуляции поведения конечного пользователя. Разные типы тестов в пирамиде выбирают разные компромиссы между быстротойобратной связи и защитой от багов. Тесты более высоких уровней пирамиды отдаютпредпочтение защите от багов, тогда как тесты нижних уровней выводят на первыйплан скорость выполнения.

Рис. 7 - Пирамида тестирования предписывает определенное соотношение юнит-,интеграционных и сквозных тестовРис. 7 - Пирамида тестирования предписывает определенное соотношение юнит-,интеграционных и сквозных тестовРис. 8 - Разные типы тестов в пирамиде принимают разные решения относительно быстрой обратной связи и защиты от баговРис. 8 - Разные типы тестов в пирамиде принимают разные решения относительно быстрой обратной связи и защиты от багов

Точное соотношение между типами тестов будет разным для разных команд и проектов. Но в общем случае должно сохраняться соотношение пирамиды: сквозныетесты составляют меньшинство; юнит-тесты большинство; интеграционные тестылежат где-то в середине.

Причина, по которой сквозных тестов меньше всего исключительно низкая скоростьвыполнения. Они также не отличаются простотой в поддержке: такие тесты обычнозанимают много места и требуют дополнительных усилий для поддержания задействованных внепроцессных зависимостей. Таким образом, сквозные тесты имеетсмысл применять только к самой критической функциональности.

У пирамиды тестирования есть исключения. Юнит-тесты менее полезны в ситуациях, в которых отсутствует алгоритмическаяили бизнес-сложность, они быстро вырождаются в тривиальные тесты. В то жевремя интеграционные тесты полезны даже в таких случаях; каким бы простым кодни был, важно проверить, как он работает в интеграции с другими подсистемами(например, базой данных). В результате в CRUD-приложениях у вас будет меньшеюнит-тестов и больше интеграционных.

Другое исключение из пирамиды тестирования API, обращающиеся к единственной внепроцессной зависимости (например, базе данных). В таких приложенияхлогично задействовать больше сквозных тестов. Так как пользовательский интерфейс отсутствует, сквозные тесты будут выполняться достаточно быстро. Затратына сопровождение тоже будут не особенно велики, потому что вы работаете толькос одной внешней зависимостью базой данных.

Выбор между тестированием по принципу черного ящика и белого ящика

Тестирование по принципу черного ящика проверяет функциональность системы без знания ее внутренней структуры. Такое тестирование обычно строитсяна основе спецификаций и требований. Оно проверяет, что должно делать приложение, а не то, как оно это делает.

Тестирование по принципу белого ящика работает по противоположномупринципу. Этот метод тестирования проверяет внутренние механизмы приложения. Тесты строятся на основе исходного кода, а не на основе требованийили спецификаций.

Рис. 9 - Достоинства и недостатки тестирования по принципу черного ящика и белого ящикаРис. 9 - Достоинства и недостатки тестирования по принципу черного ящика и белого ящика

Как говорилось ранее, нельзя делать уступки в отношении устойчивоститестов к рефакторингу: тест либо хрупок, либо нет. Всегда отдавайте предпочтениетестированию по принципу черного ящика. Тесты неважно, юнит-, интеграционные или сквозные должны рассматривать систему как черный ящики проверять поведение, имеющее смысл с точки зрения бизнес-логики. Если тестне удается связать с бизнес-требованием, это является признаком хрупкости теста.

Ссылки на все части

Подробнее..

Для чего нужно интеграционное тестирование?

06.05.2021 08:19:11 | Автор: admin

Эта статья является конспектом книги Принципы юнит-тестирования. Материал статьи посвящен интеграционным тестам.

Юнит-тесты прекрасно справляются с проверкой бизнес-логики, но проверять эту логику в вакууме недостаточно. Необходимо проверять,как разные ее части интегрируются друг с другом и внешними системами: базойданных, шиной сообщений и т. д.

В этой статье рассматривается роль интеграционных тестов, когда их следует использовать и когда лучше положиться на классические юнит-тесты. Также затронем эффективное написание интеграционных тестов.

Что такое интеграционный тест?

Юнит-тест удовлетворяет следующим трем требованиям:

  • проверяет правильность работы одной единицы поведения;

  • делает это быстро;

  • и в изоляции от других тестов.

Тест, который не удовлетворяет хотя бы одному из этих трех требований, относитсяк категории интеграционных тестов.На практике интеграционные тесты почти всегда проверяют, как система работает в интеграции с внепроцессными зависимостями.

Если все внепроцессные зависимости заменить моками,никакие зависимости не будут совместно использоваться между тестами, благодарячему эти тесты останутся быстрыми и сохранят свою изоляцию друг от друга и становятся юнит-тестами. Темне менее во многих приложениях существует внепроцессная зависимость, которуюневозможно заменить моком. Обычно это база данных зависимость, не видимаядругими приложениями.

Важно поддерживать баланс между юнит- и интеграционными тестами. Работанапрямую с внепроцессными зависимостями замедляет интеграционные тесты.Кроме того, их сопровождение также обходится дороже.

С другой стороны, интеграционные тесты проходят через больший объем кода, что делает их болееэффективными в защите от багов по сравнению с юнит-тестами.Они также более отделены от рабочего кода, а, следовательно, обладают большейустойчивостью к его рефакторингу.

Соотношение между юнит- и интеграционными тестами зависит от особенностейпроекта, но общее правило выглядит так: проверьте как можно больше пограничныхслучаев бизнес-сценария юнит-тестами; используйте интеграционные тесты дляпокрытия одного позитивного пути, а также всех граничных случаев, которые непокрываются юнит-тестами.

Для интеграционного теста выберите самый длинный позитивный путь, проверяющий взаимодействия со всеми внепроцессными зависимостями. Если не существуетодного пути, проходящего через все такие взаимодействия, напишите дополнительные интеграционные тесты столько, сколько потребуется для отражениявзаимодействий с каждой внешней системой.

Какие из внепроцессных зависимостей должны проверяться напрямую

Все внепроцессные зависимости делятся на две категории.

  • Управляемые зависимости (внепроцессные зависимости, находящиеся под вашимполным контролем): эти зависимости доступны только через ваше приложение;взаимодействия с ними не видны внешнему миру. Типичный пример база данных.

  • Неуправляемые зависимости (внепроцессные зависимости, которые не находятсяпод вашим полным контролем) результат взаимодействия с такими зависимостямивиден извне. В качестве примеров можно привести сервер SMTP и шину сообщений.

Взаимодействия с управляемыми зависимостями относятся к деталям имплементации. И наоборот, взаимодействия с неуправляемымизависимостями являются частью наблюдаемого поведения вашей системы.

Рис. 1 Взаимодействия с зависимостямиРис. 1 Взаимодействия с зависимостями

Это различие приводит к тому, что такие зависимости по-разному обрабатываютсяв интеграционных тестах. Взаимодействия с управляемыми зависимостями являются деталями имплементации. Использоватьих следует в исходном виде в интеграционных тестах. Взаимодействия с неуправляемыми зависимостями являютсячастью наблюдаемого поведения системы. Такие зависимости должны заменяться моками.

Требование о сохранении схемы взаимодействий с неуправляемыми зависимостями обусловлено необходимостью поддержания обратнойсовместимости с такими зависимостями. Моки идеально подходят для этой задачи.Они позволяют обеспечить неизменность схемы взаимодействий при любыхвозможных рефакторингов.Поддерживать обратную совместимость во взаимодействиях с управляемыми зависимостями не обязательно, потому что никто, кроме вашего приложения, с ними неработает. Внешних клиентов не интересует, как устроена ваша база данных; важнотолько итоговое состояние вашей системы. Использование реальных экземпляровуправляемых зависимостей в интеграционных тестах помогает проверить это итоговое состояние с точки зрения внешних клиентов.

Иногда встречаются внепроцессные зависимости, обладающие свойствами какуправляемых, так и неуправляемых зависимостей. Хорошим примером служит базаданных, доступная для других приложений.

База данных не лучший механизм для интеграции между системами, потому чтоона связывает эти системы друг с другом и усложняет дальнейшую их разработку.Используйте это решение только в случае, если других вариантов нет. Правильнееосуществлять интеграцию через API (для синхронных взаимодействий) или шинусообщений (для асинхронных взаимодействий).

В этом случае следует рассматривать таблицы, видимые для других приложений, как неуправляемую зависимость. Такие таблицы фактически выполняют функции шины сообщений, а их строки играют роль сообщений. Используйтемоки, чтобы гарантировать неизменность схемы взаимодействий с этими таблицами. В то же время следует рассматривать остальные части базы данных как управляемую зависимость и проверять ее итоговое состояние, а не взаимодействия с ней.

Рис. 2 БД, доступная для внешних приложенийРис. 2 БД, доступная для внешних приложений

Основные приемы интеграционного тестирования

Существует несколько общих рекомендаций, которые помогут извлечь максимальную пользу из интеграционных тестов:

  • явное определение границ доменной модели (модели предметной области);

  • сокращение количества слоев в приложении;

  • устранение циклических зависимостей.

Явное определение границ модели предметной области. Доменная модель представляет собой совокупность знанийо предметной области задачи, для решения которой предназначен ваш проект. Данная практика помогает с тестированием. Юнит-тесты должны ориентироваться на доменную модель и алгоритмы, тогдакак интеграционные тесты на контроллеры. Таким образом, четкое разграничениемежду доменными классами и контроллерами также помогает отделить юнит-тестыот интеграционных.

Сокращение количества слоев. Многие разработчики стремятся к абстрагированию и обобщению кода путем введения дополнительных уровней абстракции.

Рис. 3 Типичное корпоративное приложение с несколькими слоямиРис. 3 Типичное корпоративное приложение с несколькими слоями

В некоторых приложениях находится столько уровней абстракции, что разработчик уже не может разобраться в коде и понять логику даже простейших операций.

Все проблемы в программировании можно решить путем добавления нового уровня абстракции (кроме проблемы наличия слишком большого количества уровней абстракции).
Дэвид Дж. Уилер

Лишние абстракции также затрудняют юнит- и интеграционное тестирование.Кодовые базы со слишком большим количеством слоев обычно не имеют четкойграницы между контроллерами и моделью предметной области. Старайтесь ограничиться минимально возможным количеством уровней абстракции.В большинстве серверных систем можно обойтись всего тремя: слоем доменноймодели, слоем сервисов приложения (контроллеров) и слоем инфраструктуры.

Исключение циклических зависимостей. Циклическая зависимость возникает в том случае, если два или более класса прямо иликосвенно зависят друг от друга. Типичный пример циклической зависимости обратный вызов:

public class CheckOutService{  public void CheckOut(int orderId)  {    var service = new ReportGenerationService();    service.GenerateReport(orderId, this);    /* остальной код */  }}public class ReportGenerationService{  public void GenerateReport(    int orderId,    CheckOutService checkOutService)  {  /* вызывает checkOutService при завершении генерирования */  }}

Как и в случае с избыточными уровнями абстракции, циклические ссылки создаютдополнительную когнитивную нагрузку при попытке прочитать и понять код. Циклические зависимости также усложняют тестирование. Вам часто приходитсяиспользовать интерфейсы и моки, для того чтобы разбить граф классов и изолировать одну единицу поведения.

Что же делать с циклическими зависимостями? Лучше всего совсем избавитьсяот них. Отрефакторить класс ReportGenerationService, чтобы он не зависел от CheckOutService и сделать так, чтобыReportGenerationService возвращал результат работы в виде простого значениявместо вызова CheckOutService:

public class CheckOutService{  public void CheckOut(int orderId)  {    var service = new ReportGenerationService();    Report report = service.GenerateReport(orderId);    /* прочая работа */  }}public class ReportGenerationService{  public Report GenerateReport(int orderId)  {  /* ... */  }}

Использование нескольких секций действий в тестах

Как вы, возможно, помните из предыдущего конспекта Анатомия юнит-тестов, наличие более одной секции подготовки,действий или проверки в тесте плохой признак. Он указывает на то, что тестпроверяет несколько единиц поведения, что, в свою очередь, ухудшает сопровождаемость теста. Например, если имеются два связанных сценария использования (допустим, регистрация и удаление пользователя).

  • подготовка подготовка данных для регистрации пользователя;

  • действие вызов UserController.RegisterUser();

  • проверка запрос к базе данных для проверки успешного завершения регистрации;

  • действие вызов UserController.DeleteUser();

  • проверка запрос к базе данных для проверки успешного удаления.

Лучше всего разбить тест, выделив каждое действие в отдельный тест. На первыйвзгляд это может показаться лишней работой, но эта работа окупается в долгосрочной перспективе. Фокусировка каждого теста на одной единице поведения упрощает пониманиеи изменение этих тестов при необходимости.

Исключение из этой рекомендации составляют тесты, работающие с внепроцесснымизависимостями, трудно приводимыми в нужное состояние. Например, регистрация пользователя приводит к созданию банковского счета во внешнейбанковской системе. Банк предоставил вашей организации тестовую среду, которую вы хотите использовать для сквозных тестов. К сожалению, тестовая средаработает слишком медленно; также возможно, что банк ограничивает количествообращений к этой тестовой среде. В таком сценарии удобнее объединить несколькодействий в один тест, чтобы сократить количество взаимодействий с проблемнойвнепроцессной зависимостью.

Выводы

Интеграционные тесты проверяют, как ваша система работает в интеграциис внепроцессными зависимостями.

Интеграционные тесты покрывают контроллеры; юнит-тесты покрывают алгоритмы и доменную модель.

Интеграционные тесты обеспечивают лучшую защиту от багов и устойчивостьк рефакторингу; юнит-тесты более просты в поддержке и дают более быструюобратную связь.

Порог для написания интеграционных тестов выше, чем для юнит-тестов: ихэффективность по метрике защиты от багов и устойчивости к рефакторингу должна быть выше, чем у юнит-тестов, для того чтобы скомпенсировать дополнительную сложность в поддержке и медленную обратную связь. Пирамидатестирования отражает этот компромисс: большинство тестов должны составлять быстрые и простые в поддержке юнит-тесты при меньшем количествемедленных и более сложных в поддержке интеграционных тестов, проверяющихправильность системы в целом.

Управляемые зависимости представляют собой внепроцессные зависимости, доступ к которым осуществляется только через ваше приложение. Взаимодействияс управляемыми зависимостями не видимы извне. Типичный пример базаданных приложения.

Неуправляемые зависимости внепроцессные зависимости, доступные длядругих приложений. Взаимодействия с неуправляемыми зависимостями видныснаружи. Типичные примеры сервер SMTP и шина сообщений.

Взаимодействия с управляемыми зависимостями являются деталями имплементации; взаимодействия с неуправляемыми зависимостями являются частьюнаблюдаемого поведения вашей системы.

Иногда внепроцессная зависимость обладает свойствами как управляемых, таки неуправляемых зависимостей. Типичный пример база данных, доступная длядругих приложений. Наблюдаемую часть такой базы следует интерпретироватькак неуправляемую зависимость; заменяйте ее моками в тестах. Рассматривайтеостальную часть зависимости как управляемую проверяйте ее итоговое состояние, а не взаимодействия с ней.

Выделите явное место для модели предметной области в коде. Четкая границамежду классами предметной области и контроллерами помогает отличать юниттесты от интеграционных.

Лишние уровни абстракции отрицательно влияют на вашу способность пониматькод. Постарайтесь свести количество этих уровней к минимуму. В большинствебэкенд-систем достаточно всего трех слоев: предметной области, сервисов приложения и инфраструктуры.

Циклические зависимости увеличивают когнитивную нагрузку при попыткахразобраться в коде. Типичный пример обратный вызов (когда вызываемаясторона уведомляет вызывающую о результате своей работы).

Множественные секции действий в тестах оправданны только в том случае, еслитест работает с внепроцессными зависимостями, которые трудно привести в нужное состояние. Никогда не включайте несколько действий в юнит-тест, потомучто юнит-тесты не работают с внепроцессными зависимостями. Многофазныетесты почти всегда принадлежат к категории сквозных.

Ссылки на все части

Подробнее..

В чём сила дашбордов, как тестировать JS-библиотеки и чего стоит выпустить собственный фреймворк в open source

04.07.2020 12:05:52 | Автор: admin
Пост посвящается всем, кто виртуально не добрался до нашего онлайн-митапа, который мы посвятили инструментам автоматического тестирования. Без лишних слов публикуем видео с BugsBusters 2020 смотрите прямо сейчас, будет хорошее начало выходных.



Сила дашбордов

Егор Иванов, специалист по автоматизации тестирования (Яндекс.Деньги)

Расскажу, как и для чего команда интеграционного тестирования стала использовать дашборды. Поделюсь, какой профит мы получаем от этого инструмента и почему считаем его важным.


Таймкоды
0:55 Каким специалистам будет полезен доклад
1:10 Что такое дашборд? Примеры из жизни. Определение термина, основные типы.
4:05 Знакомство с командой интеграционного тестирования. Схема взаимодействия инструментов: Jira, Autorun, Locker, Pinger, Jenkins
7:32 Что делать, когда что-то идет не так роль дежурного
8:15 Дашборд дежурного: мастштабирование задач, использование Grafana
11:26 Как происходит отсылка метрик. Типы метрик.
13:09 Процесс отправки метрик из Java и sh
14:10 Как построить дашборд? Как можно использовать дашборды?
15:00 Пример 1 дашборд как визуализатор метрик
18:20 Пример 2 дашборд как мотиватор
22:18 Пример 3 дашборд для анализа
24:45 Пример 4 дашборд для экономии времени
27:00 Подведение итогов: что мы получили от внедрения дашбордов



Святой Грааль автоматизации: не можешь найти создай сам

Андрей Ганин, QA Head (Альфа-Банк)

Кажется, выбор инструментов для автоматизации огромный ровно до тех пор, пока вам не понадобятся E2E-тесты на C#. Я расскажу о том, как мы создавали собственный фреймворк: о трудностях, несбывшихся надеждах и тонкостях выпуска внутреннего продукта в open source.


Таймкоды
1:30 О чем пойдет речь в докладе?
2:20 Предыстория: как Альфа-банк задумался о сокращении времени на проверку внутренних продуктов.
3:32 Выявление основной проблемы отсутствии документации.
4:21 Итоги первой реализации фреймворка
5:28 Описание второй итерации. SpecFlow. Итоги второй реализации
8:32 What if?.. Создание инструмента, который мог бы безошибочно и без установки дополнительного ПО создавать автотесты.
9:20 Схема взаимодействия внутренний инструментов AFT Desk
10:58 А зачем это всё?
13:35 Разделение тестов с фреймворком. Как это происходит внутри?
16:31 Глобальное изменение: прекращение Microsoft развития фреймворка Net Framework. Переход на Net Standard
18:20 Как изменился процесс после перехода. Плюсы и минусы
20:57 Применимость фреймворка. Примеры. Паттерны Page Object
23:11 Как использовать технологии?
24:17 Как выглядит релиз новой версии в Open Source. Различия с внутренним решением
26:44 Выводы: зачем использовать фреймворк и кому это может пригодится? Планы развития



Как мы тестируем виджет Яндекс.Кассы

Дмитрий Сергиенко, старший тестировщик (Яндекс.Деньги)
Виджет Яндекс.Кассы это JS-библиотека, которая работает через iframe. Расскажу о своём опыте тестирования и о нашем инструменте WidgetRunner.


Таймкоды:
0:32 Как тестировать JS-библиотеку?
0:54 Виджет Яндекс.Кассы: что это такое.
2:45 Почему мы решили использовать iframe
3:04 Как же это все тестировать? Первый вариант (статичный html-файл), его минусы.
3:45 О платежном токене: что это и как его получить.
5:01 Почему 1 подход не сработал? Следующие подходы
6:09 Почему плохо тестировать только форму оплаты?
7:48 Требования к инструменту тестирования
8:40 WidgetRunner как работает инструмент и его функциональность
11:52 Выводы: что получили с внедрением инструмента WidgetRunner



Наш первый митап в онлайне прошел круто и драйвово: чуть больше 200 слушаталей в прямом эфире! А под конец мы еще и подарочные сертификаты разыграли в викторине участники остались довольными.

P.S. Скоро откроем регистрацию на Android-митап, на котором затронем темы мобильного тестирования. Следите за новостями!
Подробнее..

Тесты должна писать разработка (?)

19.02.2021 16:11:01 | Автор: admin
Привет! Есть старый холивар на тему, кто же должен писать тесты: разработчики или тестировщики. Вроде как если в команде есть тестировщики, то логично, что тесты пишут они, правда? С другой стороны, ребята из разработки (помимо самой разработки) точно знают, как работает их код и как будет вести себя в тех или иных ситуациях. Как минимум предполагают.


Дисклеймер: меня зовут Эрик Бурыгин, я давно работаю тестировщиком, веду студентов на курсе Инженер по тестированию, поэтому может показаться, что тестировщик просто хочет перекинуть кусок работы на разработчиков. На самом деле у описываемого подхода есть как плюсы, так и минусы, поэтому статья носит в том числе и дискуссионный характер. Буду рад увидеть в комментах мнения как разработчиков, так и тестировщиков.

Если тесты пишет разработка, можно решить сразу несколько проблем, например:

  • Ощутимо ускорить релизный цикл.
  • Снять нагрузку с тестирования.

В большинстве команд процесс выглядит примерно так:

  1. Разработчик создаёт новые фичи и допиливает существующие.
  2. Тестировщик всё это тестирует и пишет различные тест-кейсы.
  3. Автоматизатор, оправдывая название должности, автоматизирует всё по написанным тест-кейсам из п.2.

Вроде бы всё выглядит просто.

Но в этой парадигме есть слабые места.

Допустим, разработчик доделал свою фичу и благополучно отдал её в тестирование. Но фича получилась не medium rare, а откровенно сырая. Это приведёт к переоткрытию задачи и дополнительным фиксам, причём итераций может быть от одной до N, в зависимости от размера этой фичи, её сложности, влияния на смежные процессы, добросовестности самого разработчика. А ещё от того, как у вас в принципе устроены процессы внутри разработки, насколько тщательно смотрятся пул-реквесты, запускается ли приложение перед отправкой на тестирование.

В общем, переменных хватает.

После того как задача будет протестирована и готова к релизу, тестированию нужно написать на весь функционал тест-кейсы. Затем сделать регресс/смоук и наконец зарелизить.

Автоматизатор же после получения написанных тест-кейсов покрывает тестами функционал. Здесь есть довольно большая вероятность, что задача попадёт в существующую очередь, поэтому тесты будут написаны с задержкой.

Просто нужно больше разработчиков


Увы, не панацея. Скорее наоборот. Чем больше в этой схеме у вас будет разработчиков, тем сильнее будет нагрузка на тестирование. В итоге будет увеличиваться или релизный цикл, или сама команда тестирования.

А это по принципу домино увеличит нагрузку уже на автоматизаторов, которым придётся обрабатывать всё большее и большее количество тест-кейсов, падающих на них от тестирования. Будет зеркальная ситуация: или увеличится время покрытия тестами, или штат автоматизаторов.

На восемь разработчиков обычно приходится два тестировщика и один автоматизатор. Автоматизация при этом не участвует в релизном цикле напрямую скорее находится поблизости. И возникает вопрос: как сделать описанные процессы более эффективными, да ещё и не потерять в качестве?

Давайте попробуем сдвинуть этап автоматизации с третьего места на первое, на этап разработки.

Что получится


Получится сразу неплохой набор плюсов, смотрите:

  • разработчики пишут тесты одновременно с написанием самой фичи, что существенно улучшает её качество;
  • падает нагрузка на тестирование: тестировщикам теперь надо посмотреть результаты тестов и оценить, в достаточной ли степени задача покрыта тестами;
  • ручного регресса в схеме больше нет за ненадобностью.

А что тестировщики?

Тестировщик и в обновлённой парадигме остаётся экспертом в тестировании именно он ревьюит как качество, так и полноту покрытия автотестами той или иной фичи, а также занимается анализом сложных и необычных проблем. Но теперь, благодаря снижению нагрузки, у тестировщика освобождается часть времени, он может заниматься процессами.

При этом надо понимать, что ручное тестирование всё равно никуда не денется у вас всегда будет что-то, что по какой-то причине автоматизировать либо невозможно, либо не имеет смысла.

Так вот, к новой парадигме. Круто же? Да хоть прямо сейчас внедрять. Если получится сделать две вещи.

  1. Продать эту идею разработке. Потому что далеко не каждый разработчик сразу захочет писать тесты, ведь это может быть скучно, или он просто не хочет: у вас, вообще-то, тестировщики вон там для чего сидят?
  2. Продать эту идею менеджерам. Потому что при прочих плюсах у вас увеличивается время разработки каждой фичи.

Какие минусы тут могут вас поджидать.

  1. Большая часть разработчиков просто не умеет тестировать, потому что они разработчики, а не тестировщики. И это нормально. Тут вы можете или научить их тестировать, что будет не самой тривиальной задачей, или просто писать тест-кейсы для них. Что де-факто ломает сам процесс.
  2. На старте автоматизация будет занимать больше времени, ведь не будет кодовой базы тестов, инфраструктуры и привычных подходов задача-то новая.
  3. Будут нужны понятные отчёты для тестирования. Но имейте в виду: даже самый понятный отчёт не всегда можно сразу научиться правильно читать.
  4. Не каждую задачу вы сможете легко и быстро покрыть тестами. В ряде случаев вам придётся на тесты тратить больше времени, чем на саму реализацию фичи.
  5. Масштабные задачи будет сложно отдавать одновременно с тестами, это занимает довольно много времени.
  6. Для этих же масштабных и сложных задач надо будет всё равно закладывать время на то, чтобы просто в них вникнуть, потому что нет другого способа проверить правильность тестов, которые писали разработчики.

Что же делать?




В принципе у каждой из проблем есть решение.

  1. Разработчики не умеют тестировать.
    Можно консультировать их на первых этапах, чтобы помочь разобраться.
  2. Нет кодовой базы, инфраструктуры и подходов.
    Всё решается только временем. На размеченные функции писать тексты проще.
  3. Понятные отчёты.
    В отчёты надо включать все шаги, а название каждого теста должно сразу отражать, что именно им проверяется.
  4. Приходится тратить много времени на ряд задач.
    Спасает здравый смысл: не всё стоит покрывать здесь и сейчас.
  5. Сложно отдавать задачи, когда нет подходов и инструментов.
    Тоже решается постепенно, главное время для анализа и внедрения того или иного инструмента. И это должно быть отдельной задачей.
  6. Проблема масштабных задач.
    Их можно сразу отдавать без тестов либо частично покрытыми тестами. Но это в любом случае не отменяет погружения в контекст.

Вывод


Подход со всеми своими плюсами и минусами имеет право на жизнь. А если ещё и правильно настроить процессы, то это поможет вам и релизный цикл ускорить, и штат не раздувать (:
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru