Что такое юнит тест

Зачем нужны юнит-тесты

Авторизуйтесь

Зачем нужны юнит-тесты

Что такое юнит тест. Смотреть фото Что такое юнит тест. Смотреть картинку Что такое юнит тест. Картинка про Что такое юнит тест. Фото Что такое юнит тест

Многие разработчики говорят о юнит-тестах, но не всегда понятно, что они имеют в виду. Иногда неясно, чем они отличаются от других видов тестов, а порой совершенно непонятно их назначение.

Доказательство корректности кода

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

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

Отличие от других видов тестов

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

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

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

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

И все-таки, что такое юнит?

Часто встречается мнение, что юнит — это класс. Однако это не всегда верно. Например, в C++, где классы не обязательны.

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

Юнит — это маленький самодостаточный участок кода, реализующий определенное поведение, который часто (но не всегда) является классом.

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

Отсутствие сцепления необходимо для написания юнит-тестов.

Другие применения юнит-тестов

Кроме доказательства корректности, у юнит-тестов есть еще несколько применений.

Тесты как документация

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

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

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

Разработка через тестирование

При разработке через тестирование (test-driven development, TDD) вы сначала пишете тесты, которые проверяют поведение вашего кода. При запуске они, конечно, провалятся (или даже не скомпилируются), поэтому ваша задача — написать код, который проходит эти тесты.

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

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

И, поскольку TDD предполагает, что нет участков кода, не покрытых тестами, все поведение написанного кода будет документировано.

Возможность лучше разобраться в коде

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

Источник

Unit-тесты: что, как и когда тестировать?

Тестирование программного кода — кропотливый и сложный процесс. Львиную долю работы в нем совершают unit-тесты. Пока они не «загорятся зеленым», тестировать дальше смысла нет.

Как же писать unit-тесты правильно? Стоит ли гнаться за 100% покрытием? С какими сложностями приходится сталкиваться инженерам на практике? Своим опытом делятся Marc Philipp и Всеволод Брекелов.

Что такое юнит тест. Смотреть фото Что такое юнит тест. Смотреть картинку Что такое юнит тест. Картинка про Что такое юнит тест. Фото Что такое юнит тест

Что такое юнит тест. Смотреть фото Что такое юнит тест. Смотреть картинку Что такое юнит тест. Картинка про Что такое юнит тест. Фото Что такое юнит тестMarc Philipp – один из основных разработчиков фреймворка JUnit 5 – инструмента для Java-тестировщиков. В данный момент работает в качестве инженера в немецкой компании LogMeIn над облачными SaaS-решениями.

Что такое юнит тест. Смотреть фото Что такое юнит тест. Смотреть картинку Что такое юнит тест. Картинка про Что такое юнит тест. Фото Что такое юнит тестВсеволод Брекелов — Senior QA Engineer в компании Grid Dynamics, более 5 лет занимается тестированием, имеет опыт построения автоматизации тестирования с нуля.

— В статьях про unit-тестирование в качестве примеров обычно приводят тестирование методов и классов калькулятора. Такие примеры могут показать сложность реальных задач? С чем приходится сталкиваться тестировщику полнофункциональных программ?

Marc Philipp: Действительно, на примерах с калькулятором невозможно показать сложность реальных задач. Они выбраны в статьях для того, чтобы читатели могли сосредоточиться на понимании подходов unit-тестирования без необходимости разбора сложного кода. Хотя эти примеры очень простые, они хорошо демонстрируют основную идею и принципы unit-тестирования. В реальной жизни тестируемый код должен быть изначально написан с учетом того, что по нему будет проводиться Unit-тестирование. Один из способов обеспечить это — писать тесты до написания кода или практически одновременно с ним. Когда у вас есть код, адаптированный к тестированию, написание unit-тестов не на много сложнее, чем для калькулятора.

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

К примеру, по запросу «unit тестирование java» можно быстро найти статью на Хабре. Она опубликована довольно давно, но не потеряла своей актуальности.

Что касается особенностей работы, я бы выделил следующие группы тестировщиков (надеюсь никого не обидеть):

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

— Каждый тест должен проверять одну вещь. Насколько полно на практике удается выполнить это условие? Как вы боретесь с зависимостями, какие фреймворки используете?

Marc Philipp: При написании unit-тестов обычно берется один образец входных данных из класса эквивалентности в тестируемой проблемной области. Конечно, вы должны сначала определить эти самые классы эквивалентности. В каждом тесте вы добавляете assertion только для тех свойств, которые релевантны вашему тесту. Не следует копипастить одни и те же assertions в каждый новый тест и прогонять их. Когда у вас есть зависимости, влияющие на работу юнита, подумайте об использовании стабов или моков, чтобы сохранить независимость теста.

Многие наши юнит-тесты для JUnit 5 используют моки, создаваемые mocking-фреймворком (Mockito в нашем случае). Как я уже говорил выше, они очень полезны для тестирования изолированного кода. Главная задача при этом — убедиться, что ваш мок ведет себя аналогично реальному коду. В противном случае тесты станут бессмысленными.

Всеволод Брекелов: Да, есть мнение: один юнит тест — один assertion. На практике такое я видел очень редко. Думаю, что это уже философия команды. Множественные assertions вполне себе имеют место.

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

Так как я пишу на Java/JavaScript(Angular), то использую обычные популярные тулы:
на Java – Mockito/EasyMock. Для компонентных тестов написать свой responsive mock — тоже хорошая идея! Всем советую.

JavaScript – ngMock. Кстати, для компонентых тестов очень классная тема – AngularPlayground.

— Как найти компромисс между трудовыми и финансовыми затратами на тестирование и качеством итогового софта при реализации «горящих» проектов? Как обычно вы аргументируете важность полноценного тестирования в таких случаях?

Marc Philipp: По моему опыту, вы не можете спасти «горящий» проект, пропустив тесты. Написание unit-тестов является неотъемлемой частью разработки программного обеспечения. Без него у вас нет возможности узнать, действительно ли ваш код выполняет то, что, по вашему мнению, он должен делать. Вы не сможете ничего быстро починить, так как не поймете, где что сломалось. Как сказал UncleBob, «единственный способ быстро поехать — это хорошо идти».

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

Очень важно организовать процесс, чтобы избежать внезапных багов и неправильно реализованных требований. Что такое правильный процесс? Конечно, есть Agile Manifesto, на который многие смотрят при организации процесса, но все равно что-то не выходит. Можно взять и построить процесс ради процесса. А можно и наоборот, последовать за http://programming-motherfucker.com/.

Мне кажется, главное – иметь требования, детализация которых устраивает разработчиков и тестировщиков в команде. Это значит, что у них одинаковое понимание того, что будет на выходе.

— Какие приемы помогают сократить время и трудовые затраты на тестирование?

Marc Philipp: «Тестирование» — перегруженный термин. Это может означать что угодно: модульное тестирование, ручное тестирование, тестирование производительности… По моему опыту, ручное тестирование, то есть ручное выполнение плана пошагового прохождения тестовых примеров, действительно дорого и часто не так эффективно, как вы думаете. Более того, автоматизация этих скучных тестов имеет смысл только в определенной степени. Тем не менее, вы должны действительно следовать тестовой пирамиде, а не писать слишком много этих end-to-end/UI тестов. Большинство ваших тестов должны быть реальными unit-тестами: независимые, быстрые тесты, которые вы можете выполнять очень часто. Написание этих тестов относительно дешево, особенно если вы знаете свои инструменты. Они очень надежны, поэтому вы не будете тратить время на их актуализацию. UI и Integration тесты всегда будут более хрупкими из-за огромного количества задействованных компонентов.

Всеволод Брекелов: Есть хороший прием — писать меньше кода.

Главное – это понимание процесса и того, что вы хотите решить (или протестировать).
Всегда нужно адекватно оценивать бюджет и время. Что это значит? Если вы можете себе позволить вливать кучу денег в приближение к 100% coverage — why not? Хозяин – барин.

Если у вас нет денег на автотесты (которые, как известно, отбиваются в основном в долгоиграющих проектах), то толпа ручных тестировщиков – ваш вариант.

Если не впадать в крайности, то самая частая ошибка — это написание e2e тестов пачками до потери пульса до того, как написаны юнит тесты, компонентные тесты, интеграционные тесты на Backend, Frontend, DB, Performance и тд. Эта тенденция, вероятно, следует от модных BDD подходов (я их не очень люблю). К чему это все приводит?

Первая степень «опьянения» — у вас начинает реально работать автоматизация. Ручные тест кейсы вы заменяете на автоматические. Тестировщики начинают радоваться. Менеджеры начинают думать, что вот-вот сэкономят.

Вторая степень — тестов становится много, почему-то некоторые из них периодически падают. Тестировщики уже не очень рады. Нужно сидеть и разбираться в причинах. А баги все равно пролезают. И, вероятно, даже находятся на QA окружении путем ручного (может, даже monkey) тестирования.

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

Четвертая степень — строить целые суперархитектуры по запуску 500 e2e тестов на 50 агентах, чтобы все летало быстро, аж за 10 минут (я тут утрирую, конечно). И все равно баги есть.

Пятая степень — я назову ее недостижимой. Приходит осознание того, что бОльшая часть e2e тестов не нужна. Нужны другие тесты, которых никто никогда не писал. Например, компонентные тесты на back-end или они же на UI. А может, не они, может, системные тесты? А может, и тесты на верстку? А может, Ваш, вариант?

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

— Как влияет на инструменты и подходы тестировщиков развитие средств разработки и подходов к созданию кода? Что из новшеств облегчает
unit-тестирование (например, представление методов в виде лямбда-функций)?

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

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

Что облегчает тестирование — странный вопрос. Думаю, что технологии не могут сильно облегчить жизнь. Так как, чтобы использовать что-то новое (технология, инструмент), его нужно изучить всей команде, принять какую-ту «полиси», code style. Это в перспективе может, конечно, облегчить жизнь, но на коротких дистанциях не очень полезно, так как трудозатратно, имхо.

Кстати, вариант перехода на Kotlin (если мы говорим про Java тесты) – может и неплохая идея. Я в своей практике пока не пробовал.

Касательно новшеств языка (лямбды и прочие полезности) — это все хорошо, конечно, но мне трудно сказать, насколько они облегчают жизнь, так как нужно это измерить. Я не измерял. Но только не записывайте меня в противники прогресса, я считаю, что практика по изучению/использованию чего-то нового должна присутствовать всегда. Это обычная continuos improvement история.

— Насколько вы покрываете unit-тестами ваши продакшн проекты? Стоит ли тратить время на 100% покрытие?

Marc Philipp: В зависимости от языка программирования и фреймворков, которые вы используете, в проекте может быть некоторый шаблонный код, который не содержит никакой логики. Но кроме таких кусков, на мой взгляд, вы должны написать unit-тесты для всего вашего кода. Таким образом, я бы посоветовал охват более 90%.

Всеволод Брекелов: В проектах, в которых мне приходилось работать, чаще всего разработчики стараются довести тесты до покрытия в 90%. Стоит ли тратить время – обычно решается менеджерами. Я не менеджер, но по мне юнит тесты – это очень хорошая практика, 100% покрытие хорошо иметь, когда есть на это ресурсы.

Главное, надо помнить, что 100% покрытие, к сожалению, не гарантирует, что у вас нет багов.

Из того, что кажется более полезным, чем гонка с 90% до 100% coverage, — это написание мутационных тестов. Ничего не скажу нового относительно статьи 2012 года. Но на практике не очень часто видел, чтобы применяли этот подход (да и сам я тоже, каюсь). Так может быть пора начинать?

— Как тестовые фреймворки помогают с unit-тестами? Какую часть работ они берут на себя? Чего не стоит ждать при использовании фреймфорков?

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

— Какие элементы кода сложнее всего поддаются unit-тестированию? Как решается эта проблема у вас?

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

Например, сложно написать unit-тест, когда, скажем, конструктор объекта содержит в себе вермишели кода, но тогда можно советовать товарищам прочитать книжки,
например и ввести code review практику.

Что касается JavaScript кода, то там можно встретиться с различными сложностями и внезапностями (да, я очень люблю JavaScript), скорее связанными с используемым фреймворком, например, работа с digest’ом. Я использовал только AngularJS/Angular2/Angular4. Несмотря на старания команды Angular сделать удобно-тестируемый фреймворк, все равно периодически сталкиваешься с проблемами, которые безусловно имеют решения, мы ведь инженеры.

Огромный массив информации о всех аспектах тестирования ждет участников на ближайшем Гейзенбаге, где Mark Phillip прочтет доклад «JUnit 5 — The New Testing Framework for Java and Platform for the JVM».

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

Источник

Школы юнит-тестирования

Что такое юнит тест. Смотреть фото Что такое юнит тест. Смотреть картинку Что такое юнит тест. Картинка про Что такое юнит тест. Фото Что такое юнит тест

Существуют две основные школы юнит-тестирования: классическая (ее также называют школой Детройта, или Чикаго) и лондонская (ее также называют мокистской школой, от слова mock).

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

Определение юнит-теста

Что же такое «юнит-тест»? Так называется автоматизированный тест, который:

проверяет правильность работы небольшого фрагмента кода (также называемого юнитом)

и поддерживая изоляцию от другого кода.

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

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

Что такое юнит тест. Смотреть фото Что такое юнит тест. Смотреть картинку Что такое юнит тест. Картинка про Что такое юнит тест. Фото Что такое юнит тест

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

Классический подход к изоляции не запрещает тестировать несколько классов одновременно, при условии что все они находятся в памяти и не обращаются к совместному состоянию (shared state), через которое тесты могут влиять на результат выполнения друг друга. Типичными примерами такого совместного состояния служат внепроцессные (out-of-process) зависимости — база данных, файловая система и т. д.

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

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

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

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

Ниже приведен тест, написанный в классическом и лондонском стиле.

А вот тот же тест, переписанный в лондонском стиле:

Фаза проверки тоже изменилась, и именно здесь кроется ключевое различие. Лондонский тест проверяет результат работы метода customer.Purchase так же, как и классический тест, но взаимодействие между Customer и Store теперь проверяется по-другому. В классическом тесте для этого используется состояние магазина. Лондонский же анализирует взаимодействия между Customer и Store: тест проверяет, какой метод и с какими параметрами Customer вызвал у Store. Для этого в мок передается вызываемый метод (x.RemoveInventory), а также сколько раз этот метод должен был вызываться в течение работы теста (Times.Once).

Отличия школ юнит-тестирования

К каким же именно отличиям приводит такое расхождение во мнениях о том, что является юнит-тестом?

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

В тесте, написанном в лондонском стиле, видно, что на мок был заменен только Store:

Причина как раз в том, что из трех аргументов метода Purchase только Store содержит внутреннее состояние, которое может изменяться со временем. Экземпляры Product (тип Product — перечисление (enum) C#) и число 5 неизменяемы.

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

Что изолируется

Размер юнита

Для чего используются моки

Любые изменяемые зависимости

Класс или набор классов

Внепроцессные (out-of-process) зависимости

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

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

Юнит-тестирование одного класса за раз

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

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

Такая тенденция понятна, но ошибочна.

Тесты не должны проверять *единицы кода* (units of code). Вместо этого они должны проверять *единицы поведения* (units of behavior) — нечто имеющее смысл для предметной области, а в идеале — нечто такое, полезность чего будет понятна бизнесу. Количество классов, необходимых для реализации такой единицы поведения, не имеет значения. Тест может охватывать как несколько классов, так и только один класс или даже всего один метод.

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

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

Пример связного рассказа:

«Когда я зову свою собаку, она идет ко мне».

Теперь сравните это со следующим рассказом:

«Когда я зову свою собаку, она сначала выставляет вперед левую переднюю лапу, потом правую переднюю лапу, поворачивает голову, начинает вилять хвостом. »

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

Юнит-тестирование большого графа взаимосвязанных классов

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

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

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

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

Выявление точного местонахождения ошибки

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

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

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

Различия в подходе к разработке через тестирование (TDD)

Лондонский стиль юнит-тестирования ведет к методологии TDD (Test-Driven Development) по схеме «снаружи внутрь» (outside-in): вы начинаете с тестов более высокого уровня, которые задают ожидания для всей системы. Используя моки, вы указываете, с какими зависимостями система должна взаимодействовать для достижения ожидаемого результата. Затем вы проходите по графу классов, пока не реализуете их все.

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

Классическая школа такой возможности не дает, потому что вам приходится иметь дело с реальными объектами в тестах. Вместо этого обычно используется подход по схеме «изнутри наружу» (inside-out или middle-out). В этом стиле вы начинаете с модели предметной области, а затем накладываете на нее дополнительные слои, пока программный код не станет пригодным для конечного пользователя.

Интеграционные тесты в двух школах

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

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

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

Итоги

Итак, подведем итоги. Юнит-тестом называется автоматизированный тест, который:

проверяет правильность работы небольшого фрагмента кода (также называемого юнитом),

и поддерживая изоляцию от другого кода.

Различия между лондонской и классической школами юнит-тестирования проистекают из несогласия относительно того, что именно означает «изоляция».

Остальные отличия между школами:

Детализированность тестов — лондонская школа почти всегда тестирует только один класс за раз. Классическая школа может охватывать несколько тестов.

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

Test-Driven Development (TDD) — лондонская школа предпочитает подход outside-in, в то время как классическая — inside-out (или middle-out).

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

Минутка рекламы. Если вам интересны подходы к тестированию, вам наверняка будет интересно и на конференции Heisenbug (онлайн, 5-7 октября). Там будет множество докладов о тестировании в самых разных его проявлениях, описания части этих докладов уже есть на сайте конференции, билеты — там же.

Источник

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

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