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

Анатомия юнит тестирования

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

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


Вначале мое определение юнит тестирования это тестирование одного продакш юнита в полностью контролируемом окружении.

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

О наследование


Постарайтесь не применять наследование. Вместо него используйте композицию зависимостей. Часто наследование применяют для реализации принципа DRY (dont repeat yourself) вынося общий код в родителя, но тем самым нарушая принцип KISS (keep it simple stupid) увеличивая сложность юнитов.

AAA (Arrange, Act, Assert) паттерн.


Если посмотреть на юнит тест, то для большинства можно четко выделить 3 части кода:
Arrange (настройка) в этом блоке кода мы настраиваем тестовое окружение тестируемого юнита;
Act выполнение или вызов тестируемого сценария;
Assert проверка, что тестируемый вызов ведет себя определенным образом.
Этот паттерн улучшает структуру кода и его читабельность, однако начинать писать тест нужно всегда с элемента Act.

Driven approach.


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

Суть в том, что код который вы пишите должен иметь причину своего существования. Важно, что бы причина была существующей, а не предполагаемой, и эта причина должна иметь в конечном итоге связь с бизнес историей.
С чего мы начинаем разработку конкретного функционала? с требований бизнеса, которые типично выглядят так: Пользователь с любой ролью должен иметь возможность создать запись, таким образом он выполнить такую то бизнес операцию.
Используя driven approach первое что мы должны сделать
  • Это создать место в UI слое, где пользователь может создать запись, скажем страницу в приложении, на которой будет кнопка Создать запись. Почему мы это сделали? потому что это требует бизнес история.
  • Кнопка Создать запись будет требовать реализации обработчика click события.
  • Обработчик события будет требовать реализации создания записи в терминах слоя бизнес логики.
  • В случае клиент-серверной архитектуры, клиент будет обращаться к некоторому end point на стороне сервера для создания этой записи.
  • Сервер в свою очередь может работать с базой данных, где такая запись должна быть создана в отдельной таблице.


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

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

AAS (Act, Assert, Setup) паттерн.



AAS этот тот же AAA паттерн, но с измененным порядком частей, отсортированных с учетом Driven approach и переименованной Arrange частью в Setup, чтобы отличать их по названию.

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

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

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

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

Принципы SOLID.



Из принципа SOLID, с точки зрения юнит тестирования очень важны 2 принципа:

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

Dependency inversion principle позволяет легко создавать и управлять сложнейшими окружениями для тестирования через IoC контейнеры. В соответствии с данным принципом, юнит должен зависеть от абстракций, что позволяет передавать ему любые реализации его зависимостей. В том числе, и не продакшен реализации, созданные специально для его тестирования. Эти реализации не имеет в себе никакой бизнес логики и созданы не только под конкретный тестируемый юнит, но и под конкретный сценарий его тестирования. Обычно они создаются с помощь одной из библиотек для mock объектов, такой как moq.

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

Качество кода


Кстати несколько слов о качестве кода тестов и продакшн. Самым качественным кодом должен быть код тестов. Причина этому одна это его размер. На 1 строку продакшн кода в среднем приходиться 2-3 строки тестового кода, то есть его в 2-3 раза больше чем продакшн кода. В этих условиях он должен хорошо читаться, быть структурированным, иметь хорошую типизацию и быть очень дружелюбным к инструментам автоматического рефакторинга. Это цели, которые достойны отдельных мероприятий и усилий.

Однотипность тестирования


Много приложения реализовано в распределенной и модульной архитектуре, где разные части написаны на различных языках, скажем клиент-серверные приложения, где клиент написан под веб на typescript и сервер написанный на c#. Важной целью для таких проектов будет приведение тестов для любой части, независимо от языка к единому подходу. Это значит, что все тесты на проекте используют AAA или AAS подход. Все тесты используют mock библиотеки с похожим API. Все тесты используют IoC. И все тесты используют одинаковые метафоры. Это позволяет повысить переносимость удачных практик на разные части проекта, упростить адаптацию новых коллег (выучил раз и применяй везде).

Моя команда создает клиент-серверные приложения, где мы используем angular на клиенте и .net core для серверной части. В следующей статье я хочу показать на примерах, как мы пишем юнит тесты под angular и с#. Как мы делаем их похожими, как располагаем в проектах, какие библиотеки применяем.
Источник: habr.com
К списку статей
Опубликовано: 21.06.2020 20:19:44
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Тестирование it-систем

Управление разработкой

Unit testing

Testing

Practices

Категории

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

© 2006-2020, personeltest.ru