Pull to refresh

Comments 22

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

И вопрос по самой статье: не являются ли тесты взаимодействия зачастую интеграционными тестами? Ведь интеграция различных объектов наверняка включает в себя их взаимодействие, хотя, наверное, не любое взаимодействие можно назвать интеграцией.
Возможно, разделение как-то можно связать с понятиями агрегации и композиции как типами ассоциации объектов?
Ответил комментарием ниже, мышка соскользнула :)
Основной мыслью статьи была мысль, что не стоит писать тесты «абы как». Что написание качественных тестов дело достаточно непростое и к коду с тестами стоит относиться также как и к коду основной системы. Как писать правильно, говорите… Как минимум, используя описанные тестовые объекты :) Я могу написать про AAA, рекомендации по именованию проектов и отдельных методов согласно рекомендациям того же Roy Osheron, если интересно. Просто думал что тема достаточно избита.

Не совсем. Интеграционные тесты — это тесты, проверяющие работоспособность двух или более модулей системы, но в совокупности, то есть нескольких объектов как единого блока. В тестах взаимодействия тестируется всё-таки определенный объект, а вместо зависимостей используются тестовые объекты, такие как моки или тестовые шпионы.
Например, есть класс, который при определенных условиях «дёргает» веб-сервис через зависимый объект. И нам надо проверить, что определенный метод зависимого объекта действительно вызывается. Логичным будет в качестве зависимого класса передать не реальный класс, работающий с вебсервисом (это будет интеграционное тестирование), и не заглушку, которую мы возможно заходим использовать при тестировании состояния, а тестовый шпион, и в конце теста проверить, что определенный метод зависимого объекта при требуемых условиях действительно был вызван (тест взаимодействия).
Спасибо, это хорошее объяснение! Мне кажется, стоит добавить одно-два предложения в этом духе в абзац про возможное превращение юнит-теста в интеграционный.
Добавил. И попутно немного переписал выводы. Рад что статья оказалась полезна!
круто-круто.

а обзор провайдеров моков, стабов, упрощение жизни программиста? риномоки, моq, moles? или как, писать все эти шпиёны-заглушки ручками?
Я бы отметил также, что тесты состояния и взаимодействия (они же тесты поведения — behaviour testing) — обычно взаимоисключающие виды тестов, которыми можно тестировать один и тот же объект (Martin Fowler отлично расписывает разницу в своей статье Mocks aren't Stubs).

И еще ремарка:
«Стоит также заметить, что модульный (unit) тест может запросто превратиться в интеграционный тест, если при тестировании используется реальное окружение (внешние зависимости) — такие как база данных, файловая система и т.д.»
Я бы не сказал, что что-то запросто во что-то превращается. Юнит-тест тестирует логику конкретного юнита, модуля. А интеграционный тест — именно взаимодействие. Подключив вместо мок-объектов реальные компоненты, мы не перестаем тестировать логику, и при этом не нужно рассчитывать, что взаимодействие протестируется «само по себе», на основе уже существующих тестов. Путаница возникает из за того, что интеграционные тесты обычно тоже _являются_ юнит тестами, в том смысле, что используется тот же тест-фреймворк, итд, но это другие тесты.

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

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

Похожи они разве что тем, что для их написания используются одинаковые фреймворки. «С технологической точки зрения интеграционное тестирование является количественным развитием модульного, поскольку так же, как и модульное тестирование, оперирует интерфейсами модулей и подсистем и требует создания тестового окружения, включая заглушки (Stub) на месте отсутствующих модулей. Основная разница между модульным и интеграционным тестированием состоит в целях, то есть в типах обнаруживаемых дефектов, которые, в свою очередь, определяют стратегию выбора входных данных и методов анализа.» Интуит
Нет, я не путаю :) Постоянно использую и то, и другое. Я как раз и зацепился за вашу фразу, что «моудльный тест может запросто превратиться в интеграционный, если при тестировании используется реальное окружеие».

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

Про разницу в подходах в статье Фаулера:

Весь параграф «Choosing Between the Differences», начиная с «I'll begin with the state versus behavior verification choice». Ну, а также, весь параграф «So should I be a classicist or a mockist?». Он повсюду подразумевает разницу и необходимость выбора.

То, что они обычно взаимоисключающие, становится понятно как-то само по себе — используя тест состояния, мы не затрагиваем поведение, а используя тестирование поведения, мы контролируем состояние. Часто бывает, что если требуется и то, и другое — стоит разбить тест на два и более, ибо он «пытается» покрыть слишком много за раз. В общем же случае, если мы попытаемся контролировать и поведение, и состояние — то, что мы, собственно, тестируем? :)
По-моему, эта статья оставляет впечатление «ууу. как там всё сложно». В конце вы заявляете, что это и было вашей целью, но зачем такая цель?
Я практически не знаю людей, которые бы писали юнит-тесты «абы как». Зато я знаю много людей, которые вообще не пишут юнит-тесты, и одна из причин как раз в том, что «там всё сложно».

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

Какие примеры? Используя какой-либо конкретный xUnit фреймворк что ли?
Может я чего-то упустил, но кратко статья состоит из следующего:
1. Тесты сложная штука, потому что
2. Есть много сложных терминов — вот они, причём каждый выдумывает себе свои варианты, от этого ещё сложнее!
3. Надеюсь после таких сложных терминов вы понял, что тестирование это супер сложный процесс

Не вижу тут никакой логики, мне начхать как всякие гуру называют фальшивые объекты которые я использую, от того что они вводят якобы сложные термины мне не чуть не сложнее создавать эти объекты
Хех, согласен с вами — создавая мок или стаб, мне пофиг, как его называют — я даже не думаю об этом. Всмысле, вообще не думаю, просто использую и все. Знаю только, что это какой-то «фейковый» объект — остальное по барабану.
Странно на самом деле так рассуждать.
Может быть вы также не используете и шаблоны проектирования, потому что «начхать, всё итак работает»?
Я бы не приводил столь категоричное сравнение — это немного другое. Я использую моки, стабы и прочие фейки, когда это необходимо, просто не думаю об этом — просто использую на автомате то, что нужно.

Хотя… может вы и правы — если кому-то об этом рассказывать, надо же называть вещи своими (разными) именами.
1. Называть нужно, но если даже гуру не пришли к единой терминологии, то можно обойтись и без терминов, сравнение с паттернами тут не уместно — они реально разные и названия у них устояшиеся, а тут отттенки одного и того же, без стандартных названий
2. Название статьи намекает на что в ней будет разговор про то как писать тесты правильно, а реально — копипаста определений терминов из книжек, об этом и был мой комент
>У данной статьи была задача показать, что написание правильных модульных тестов — достаточно сложная задача

ГДЕ?

Пока что приведена терминология, но не сама суть.
Комментаторы выше с вами не согласны :))

Какая суть-то? Как, прочитав про шаблоны тестовых объектов, теперь их создать в коде?
Наверное, использовав оператор new, или аналог.
Еще пробежался по статье — можно также упомянуть Test-Double-объекты (или Test-Specific Subclass) — наследники тестируемых объектов, созданные специально для упрощения тестирования. Я понимаю, что при использовании «Test Spy», они используются для перехвата вызовов, но и сами по себе они заслуживают отдельного внимания.


Ну да, некое «обобщение», но мне кажется это уже некоторый «перебор» что ли… :)

Можно еще про 'seams' написать, или про strict и unstrict mocks в приближении уже к более-менее конкретным isolation frameworks.
А, вот какая диаграмма на xunit-е. Мы с командами никогда не называли фейки TestDub-ами. Под TestDub я подразумевал именно Test-Specific Subclass — а у него вполне конкретный сценарий использования, кстати, достаточно часто встречающийся. Так что никакой не перебор.
Sign up to leave a comment.

Articles