Pull to refresh

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

Reading time3 min
Views58K
Большинство людей не умеют писать 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.

Надеюсь, я сумел подвести читателя к основной мысли — написание качественных модульных тестов дело достаточно непростое. Модульные тесты подчиняются тем же правилам, что и основное приложение, которое они тестируют. При написании модульных тестов следует тестировать модули настолько изолированно друг от друга, насколько это возможно, и различные разновидности применяемых тестовых объектов в этом, безусловно, помогают. Стиль написания модульных тестов должен быть таким же, как если бы вы писали само приложения — без копипастов, с продуманными классами, поведением, логикой.
Tags:
Hubs:
Total votes 61: ↑50 and ↓11+39
Comments22

Articles