что такое unit тесты с

Пишем unit тесты так, чтобы не было мучительно больно

что такое unit тесты с. Смотреть фото что такое unit тесты с. Смотреть картинку что такое unit тесты с. Картинка про что такое unit тесты с. Фото что такое unit тесты с

Любую задачу в программировании можно выполнить массой разных способов, и не все они одинаково полезны. Хочу рассказать о том, как можно накосячить при написании модульных тестов. Я пишу мобильные приложения уже 6 лет, и в моем «багаже» много разных кейсов. Уверен, что кому-то будет полезно.

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

Хорошо написанные тесты должны быть достоверными и удобными. Как этого добиться.

Тесты должны давать одинаковые результаты независимо от среды выполнения. Где бы ни был запущен тест – на локальной машине под Windows или macOS, или на CI сервере – он должен давать одинаковый результат и не зависеть от времени суток или погоды на Марсе. Единственное, что должно влиять на результат работы теста – тот код, который непосредственно проверяется в нем.

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

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

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

Тесты должны быть написаны одновременно с тестируемым кодом или раньше него.

Также эти качества известны по книгам дядюшки Боба как 5 принципов чистых тестов.

А теперь разберем плохие практики, которые я чаще всего встречал в реальных проектах.

Плохие практики

Берем хорошие практики и делаем все наоборот. А теперь об этом подробнее.

Множество проверок в одном тесте

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

Любая проверка в тесте приводит к его падению. Несколько проверок мешают понять, что конкретно сломалось.

Каждая новая проверка – причина для изменения теста. А значит, меняться такие тесты будут чаще, чем нужно.

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

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

В общем, проверок должно быть сильно меньше, чем повторений слова «проверка» в этом коротком тексте.

Пример теста с множеством проверок

Что делать? Такие тесты необходимо делить на более мелкие, содержащие одну действительно необходимую проверку.

Пример разделения теста

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

Добавлю простое эмпирическое правило: «Если не понятно, надо ли разделить тест, или можно оставить несколько проверок – надо делить».

Скрытый вызов теста

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

Пример скрытого вызова теста

Что делать? Способы борьбы аналогичны: делим один тест на несколько.

Тест, который ничего не проверяет

Обратный случай: тест вроде бы содержит проверки, но фактически они будут выполнены всегда. В приведенном примере тест проверяет, как реализовано mockito, а не юзкейс, для которого он написан.

Пример теста, который ничего не проверяет

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

Логика в тестах

Использование логики в тестах – зло. Любой логический оператор, будь то if, when, if not null, циклы и try, увеличивает вероятность ошибки. А ошибки тестов — это последнее, с чем хотелось бы разбираться. Также логика в тесте говорит о том, что в нем, скорее всего, происходит более одной проверки за раз. Страдает читаемость. Сложнее понять, как устроен тестовый метод.

Пример теста с логикой

Что делать? Разделить тест на несколько отдельных, более простых. Логические операторы удалить.

Пример разделения теста с логикой на несколько

Тестирование реализации, а не контракта (избыточное специфицирование)

Контракт класса – это совокупность его открытых методов и полей и договоренности о том, как они работают.

Типичный контракт презентера: при вызове onViewAttach происходит запрос данных, и как только они получены, view должна их отобразить, а в случае ошибки – показать диалог.

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

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

Пример теста с избыточными проверками

Что делать? Зависит от конкретного случая: где-то нужно просто удалить избыточные проверки, а где-то – разделить тест на несколько более простых.

verifyNoMoreInteractions после каждого теста (в tearDown())

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

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

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

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

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

Разделяемое состояние

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

Синтетический пример нестабильности тестов из-за разделяемого состояния

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

Из всего перечисленного можно сделать вывод: не все unit тесты одинаково полезны.

Источник

Тестирование

Введение в юнит-тесты

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

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

Мы не должны тестировать код используемого фреймворка или используемых зависимостей. Тестировать надо только тот код, который написали мы сами.

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

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

Большинство юнит-тестов так или иначе имеют ряд следующих признаков:

Тестирование небольших участков кода («юнитов»)

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

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

Тестирование в изоляции от остального кода

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

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

Тестирование только общедоступных конечных точек

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

Фрейморки тестирования

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

Данные фреймворки предоставляют несложный API, который позволяет быстро написать и автоматически проверить тесты.

Разработка через тестирование (Test-Driven-Development)

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

Порядок написания кода при TDD довольно прост:

Запускаем его и видим, что он завершился неудачей (программный код ведь еще не написан)

Пишем некоторое количество кода, достаточное для запуска теста

Снова запускаем тест и видим его результаты

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

Источник

Unit-тесты, пытаемся писать правильно, чтобы потом не было мучительно больно

100 модульных тестов и приходится тратить продолжительное время на то чтобы заставить их работать вновь. Итак, приступим к «хорошим рекомендациям» при написании автоматических модульных тестов. Нет, я не буду капитаном очевидностью, в очередной раз описывая популярный стиль написания тестов под названием AAA (Arange-Act-Assert). Зато попытаюсь объяснить, чем отличается Mock от Stub-а и что далеко не все тестовые объекты — «моки».

Глобально модульные тесты можно условно поделить на две группы: тесты состояния (state based) и тесты взаимодействия (interaction tests).

Тесты состояния — тесты, проверяющие что вызываемый метод объекта отработал корректно, проверяя состояние тестируемого объекта после вызова метода.

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

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

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

Внешняя зависимость — это объект, с которым взаимодействует код и над которым нет прямого контроля. Для ликвидации внешних зависимостей в модульных тестах используются тестовые объекты, например такие как stubs (заглушки).

Стоит заметить что существует классический труд по модульным тестам за авторством Жерарда Месароша под названием «xUnit test patterns: refactoring test code«, в котором автор вводит аж 5 видов тестовых объектов, которые могут запросто запутать неподготовленного человека:

— dummy object, который обычно передается в тестируемый класс в качестве параметра, но не имеет поведения, с ним ничего не происходит, никакие методы не вызываются. Примером таких dummy-объектов являются new object(), null, «Ignored String» и т.д.

— test stub (заглушка), используется для получения данных из внешней зависимости, подменяя её. При этом игнорирует все данные, могущие поступать из тестируемого объекта в stub. Один из самых популярных видов тестовых объектов. Тестируемый объект использует чтение из конфигурационного файла? Передаем ему ConfigFileStub возвращающий тестовые строки конфигурации для избавления зависимости на файловую систему.

— test spy (тестовый шпион), используется для тестов взаимодействия, основной функцией является запись данных и вызовов, поступающих из тестируемого объекта для последующей проверки корректности вызова зависимого объекта. Позволяет проверить логику именно нашего тестируемого объекта, без проверок зависимых объектов.

— mock object (мок-объект), очень похож на тестовый шпион, однако не записывает последовательность вызовов с переданными параметрами для последующей проверки, а может сам выкидывать исключения при некорректно переданных данных. Т.е. именно мок-объект проверяет корректность поведения тестируемого объекта.

— fake object (фальшивый объект), используется в основном чтобы запускать (незапускаемые) тесты (быстрее) и ускорения их работы. Эдакая замена тяжеловесного внешнего зависимого объекта его легковесной реализацией. Основные примеры — эмулятор для конкретного приложения БД в памяти (fake database) или фальшивый вебсервис.

Roy Osherove в книге «The Art of Unit Testing» предлагает упростить данную классификацию и оставляет всего три типа тестовых объектов — Fakes, Stubs и Mocks. Причем Fake может быть как stub-ом, так и mock-ом, а тестовый шпион становится mock-ом. Хотя лично мне не очень нравится перемешивание тестового шпиона и мок-объекта.

Запутанно? Возможно. У данной статьи была задача показать, что написание правильных модульных тестов — достаточно сложная задача, и что не всё то mock, что создаётся в посредством mock-frameworks.

Надеюсь, я сумел подвести читателя к основной мысли — написание качественных модульных тестов дело достаточно непростое. Модульные тесты подчиняются тем же правилам, что и основное приложение, которое они тестируют. При написании модульных тестов следует тестировать модули настолько изолированно друг от друга, насколько это возможно, и различные разновидности применяемых тестовых объектов в этом, безусловно, помогают. Стиль написания модульных тестов должен быть таким же, как если бы вы писали само приложения — без копипастов, с продуманными классами, поведением, логикой.

Источник

Основная идея юнит (или модульного, как его еще называют) тестирования – тестирование отдельных компонентов программы, т.е. классов и их методов. Разрабатывать код, покрытый тестами, весьма полезно, потому что при их правильном использовании практически исключается возможность регресии в истории развитии программы – «что-то новое добавили, половина старого слегла». Также сейчас весьма модна методология разработки “TDD” — Test Driven Development. Согласно ей, программист вначале разрабатывает набор тестов для будущей функциональности, просчитывает все варианты выполнения, и лишь потом начинает писать непосредственно рабочий код, подходящий под уже написанные тесты.

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

Работаю я в основном в Visual Studio, пишу на шарпе, а значит выбор был почти ограничен двумя продуктами – Nunit и Unit Testing Framework.

Unit Testing Framework — это встроенная в Visual Studio система тестирования, разрабатываемая Майкрософт, постоянно развивающаяся( в числе последних обновлений – возможность тестирования UI, о чем уже писали на хабре), и что немаловажно, она почти наверняка будет существовать все время, пока есть Visual Studio, чего не скажешь о стронних разработках. Отличная интеграция в IDE и функция подсчета процента покрытия кода в программе окончательно склонили чашу весов – выбор был сделан.
В сети присутствует немаленькое количество разнообразных туториалов по тестированию, но все они обычно сводятся к тестированию самописного калькулятора или сравнению строк. Это вещи, конечно, тоже необходимые и важные, но на серьезные примеры они тянут плохо, если сказать откровенно – совсем не тянут. Такие задачи я и сам могу протетстировать даже в уме.

Вот список более серьезных задач
• проверка корректности создания БД
• проверка корректности работы бизнес-логики
• получение пользы от всего этого(в моем случае польза была получена))

Тестирование БД

Unit Testing Framework позволяет тестировать разнообразные аспекты работы БД – проверка схемы, количества записей в таблицах, хранимых процедур, времени выполнения запросов, их результатов и многое другое.

Начинаем тесты


Создаем новое решение типа TestProject. Назовем его LinkCatalog.Tests. И добавляем в него новый тест Database Unit Test
что такое unit тесты с. Смотреть фото что такое unit тесты с. Смотреть картинку что такое unit тесты с. Картинка про что такое unit тесты с. Фото что такое unit тесты с
Появляется окно настройки соединения с БД. Настраиваем соединение с нашей БД и жмем ОК. В этом же окне можно указать параметры автогенерации данных для тестов. Эта функция использует Data Generation Plan и позволяет заполнить таблицу базы тестовыми значениями, используя шаблоны и даже регулярные выражения.
Нажимаем ОК и попадаем на окно тестирования БД.

Тест №1

Теперь устанавливаем условие корректности теста. Как я говорил, существует большой список всевозможных критериев, но нас интересует один из самых простых – ScalarValue.
что такое unit тесты с. Смотреть фото что такое unit тесты с. Смотреть картинку что такое unit тесты с. Картинка про что такое unit тесты с. Фото что такое unit тесты с
Все опции условия настраивается в окошке Properties.
что такое unit тесты с. Смотреть фото что такое unit тесты с. Смотреть картинку что такое unit тесты с. Картинка про что такое unit тесты с. Фото что такое unit тесты с

Все! На этом первый тест закончен. Запускаем и смотрим
что такое unit тесты с. Смотреть фото что такое unit тесты с. Смотреть картинку что такое unit тесты с. Картинка про что такое unit тесты с. Фото что такое unit тесты с
Что и требовалось доказать – строки успешно хранятся в базе.

Тест №2

Теперь настало время заняться уже более реальной проверкой, чем количества записей. Речь идет о хранимой процедуре. А вдруг разработчики базы допустили в ней критическую ошибку? А ведь это важнейшая часть работы подсистемы аутентификации пользователей!
Создаем новый тест БД, нажав на зеленый плюсик
что такое unit тесты с. Смотреть фото что такое unit тесты с. Смотреть картинку что такое unit тесты с. Картинка про что такое unit тесты с. Фото что такое unit тесты с
что такое unit тесты с. Смотреть фото что такое unit тесты с. Смотреть картинку что такое unit тесты с. Картинка про что такое unit тесты с. Фото что такое unit тесты с

SELECT * FROM Users WHERE >

Здесь уже используется другое условие – ожидается, что в выборке будет ровно 1 строка.
что такое unit тесты с. Смотреть фото что такое unit тесты с. Смотреть картинку что такое unit тесты с. Картинка про что такое unit тесты с. Фото что такое unit тесты с

Этот тест также завершается успешно, что не может не радовать.
что такое unit тесты с. Смотреть фото что такое unit тесты с. Смотреть картинку что такое unit тесты с. Картинка про что такое unit тесты с. Фото что такое unit тесты с
По результатам тестирования базы данных можно сказать, что она работает стабильно и ожидаемо. Сейчас, покрытие процедур БД достигает 100% — предела, которого непросто добиться в более сложном приложении или базе с бОльшим количеством таблиц/процедур/связей.

Юнит-тесты кода

namespace LinkCatalog.DAL
<
public class UserModel
<
.
public static int GetUserIdByName(string username)
<
string query = » SELECT ID FROM Users WHERE Login = @login;»;
DB. get ().CommandParameters. Add ( new SqlParameter(«@login», username));

return id;
>
>
public class DB
<
.
private static DB instance;

public static DB get ()
<
if (instance == null )
instance = new DB();

private SqlConnection connection ;
private SqlDataReader reader;

public List CommandParameters;
private DB()
<
this. connection = new SqlConnection(WebConfigurationManager.ConnectionStrings[«DBConnectionString»].ConnectionString);
this.CommandParameters = new List();
>

public object GetOneCell(string query)
<
SqlCommand sc = new SqlCommand(query, this. connection );

object res = sc.ExecuteScalar();
this.CommandParameters.Clear();

using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;

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

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

configSections >
section name =»DatabaseUnitTesting» type =»Microsoft.Data.Schema.UnitTesting.Configuration.DatabaseUnitTestingSection, Microsoft.Data.Schema.UnitTesting, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a» />
configSections >
DatabaseUnitTesting >
DataGeneration ClearDatabase =»true» />
ExecutionContext Provider =»System.Data.SqlClient» ConnectionString =»Data Source=SIRIXPC\sqlexpress;Initial Catalog=TestDB;Integrated Security=True;Pooling=False»
CommandTimeout =»30″ />
PrivilegedContext Provider =»System.Data.SqlClient» ConnectionString =»Data Source=SIRIXPC\sqlexpress;Initial Catalog=TestDB;Integrated Security=True;Pooling=False»
CommandTimeout =»30″ />
DatabaseUnitTesting >

connectionStrings >
add name =»DBConnectionString» connectionString =»Data Source=SIRIXPC\SQLEXPRESS;Initial Catalog=tbd;Integrated Security=True»
providerName =»System.Data.SqlClient» />
connectionStrings >

Теперь все ОК!
что такое unit тесты с. Смотреть фото что такое unit тесты с. Смотреть картинку что такое unit тесты с. Картинка про что такое unit тесты с. Фото что такое unit тесты с
Вот так вот тестирование помогает выявить некоторые ошибки в программе, пускай они возникают от забывчивости/лени/не знания, но так их исправить легче, чем на живом сервере. Этот топик, естественно, не покрывает всех аспектов тестирования программ и лишь приоткрыл эту область. За кадром осталась возможность условных тестов, зависящих от других тестов, тестов пользовательских интерфейсов и другие вещи.

Тестируйте свои программы и пускай багов у Вас будет мало, а фич много-много!

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *