Новости

15.12.2020

Книга «Принципы юнит-тестирования»

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

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

В этой книге: — Универсальные рекомендации по оценке тестов. — Тестирование для выявления и исключения антипаттернов. — Рефакторинг тестов вместе с рабочим кодом. — Использование интеграционных тестов для проверки всей системы.

Для кого написана эта книга

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

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

Переиспользование тестовых данных между тестами


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

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

Первый (неправильный) способ переиспользования тестовых данных — инициализация их в конструкторе теста (или методе, помеченном атрибутом [SetUp], если вы используете NUnit), как показано в листинге 3.7.

Два теста в листинге 3.7 имеют общую логику конфигурации. Они содержат одинаковые секции подготовки, а следовательно, эти секции можно полностью выделить в конструктор CustomerTests — именно это и было сделано выше.

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

— он создает сильную связность (high coupling) между тестами;
— он ухудшает читаемость тестов.

Обсудим эти недостатки более подробно.

Сильная связность (high coupling) между тестами как антипаттерн

В новой версии, приведенной в листинге 3.7, все тесты связаны друг с другом: изменение логики подготовки одного теста повлияет на все тесты в классе. Например, если заменить строку

строкой

то тесты, ожидающие 10 единиц шампуня на складе, начнут падать.

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

Чтобы следовать этим правилам, необходимо избегать совместного состояния (shared state) в классах тестов. Следующие два приватных поля служат примерами такого совместного состояния:

 

Использование конструкторов в тестах ухудшает читаемость

Другой недостаток выделения кода подготовки в конструктор — ухудшение читаемости теста. С таким конструктором просмотр самого теста больше не дает вам полной картины. Чтобы понять, что делает тест, вам приходится смотреть в два места: сам тест и конструктор тест-класса.

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

3.3.3. Более эффективный способ переиспользования тестовых данных

Использование конструктора — не лучший подход к переиспользованию тестовых данных. Второй (правильный) способ — написать фабричные методы, как показано в листинге 3.8.

Листинг 3.8. Выделение общего кода инициализации в приватные фабричные методы

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

К примеру, возьмем следующую строку:

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

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

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

Листинг 3.9. Общий код инициализации в базовом классе

Обратите внимание на то, что класс CustomerTests остается без конструктора. Он получает доступ к экземпляру _database, наследуя его от базового класса IntegrationTests.

Об авторе


Владимир Хориков — разработчик, Microsoft MVP и автор на платформе Pluralsight. Профессионально занимается разработкой программного обеспечения более 15 лет, а также обучением команд тонкостям юнит-тестирования. За последние годы Владимир опубликовал несколько популярных серий в блогах, а также онлайн-курс на тему юнит-тестирования. Главное достоинство его стиля обучения, которое часто отмечают студенты, — сильная теоретическая подготовка, которая затем используется на практике.


Комментарии: 0

Пока нет комментариев


Оставить комментарий






CAPTCHAОбновить изображение

Наберите текст, изображённый на картинке

Все поля обязательны к заполнению.

Перед публикацией комментарии проходят модерацию.