Юнит-тестирование

Всё про тестирование в Yii 2.0
Ответить
sda
Сообщения: 334
Зарегистрирован: 2013.12.19, 09:29

Юнит-тестирование

Сообщение sda »

Могут ли юнит-тесты делать сетевые походы или обращаться к файловой системе? Просто много разговоров о том, что сетевые походы надо мокать. Но в любом же приложении будет некоторое количество самых низкоуровневых инфраструктурных сервисов, которые используют возможности самого языка программирования например таких http://php.net/manual/en/ref.stream.php или построены поверх готовых библиотек. Я не понимаю, почему некоторые называют такие тесты интеграционными только на основе того, что юнит ходит в сеть или файловую систему и почему я должен писать уродские юнит-тесты пытаясь замокать с дюжину функций из самого языка программирования. В какой момент в головах у таких людей возникает два юнита, что тест вдруг каким-то магическим образом превращается из юнит в интеграционный?

Смотрю разные библиотеки работающие с сетью и везде есть небольшое количество классов и соответственно юнит-тестов которые ходят в сеть. Эти классы работающие с сетью затем используются в более высокоуровневых классах и там они уже естественно внедряются как зависимости и подменяются на моки в тестах. Пример https://github.com/yiisoft/yii2-httpcli ... stCase.php или doctrine или всякие очереди на redis и прочее. Везде есть небольшое количество медленных юнит-тестов работающих с сетью. При этом в различных статьях пишут, что это ошибка когда юнит-тесты ходят в сеть. Для меня это странно писать сетевой класс и при этом пытаться убить сеть.

Где я заблуждаюсь?
Nex-Otaku
Сообщения: 831
Зарегистрирован: 2016.07.09, 21:07

Re: Юнит-тестирование

Сообщение Nex-Otaku »

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

То есть юнит-тест проверяет логику конкретного участка кода, самого класса, без учёта внешней среды. Поэтому логично вынести всё, что относится к внешней среде, в отдельные объекты.

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

Тут главный вопрос, что именно ты хочешь проверить тестом. Если ты тестируешь сам класс, его собственное поведение и тебе неважно, как ведёт себя при этом сеть, можно подменить её и упростить, ускорить тест. Если же ты хочешь именно проверить взаимодействие класса с реальной сетью, тогда тестируй на сети. И если ты правильно вынес все эти внешние зависимости из класса, то всегда можешь сделать выбор, как тестировать - на реальной сети или моках.
sda
Сообщения: 334
Зарегистрирован: 2013.12.19, 09:29

Re: Юнит-тестирование

Сообщение sda »

Nex-Otaku вот здесь http://www.taimila.com/blog/ddd-and-tes ... ence-layer человек пишет, что предпочитает не мокать хранилище использующееся в репозиториях и тестировать на реальной субд. Я с ним согласен. Ты же не можешь до бесконечности выносить зависимости из класса. В конце всё равно получится самый низкоуровневый класс из которого уже ничего нельзя вынести и при этом он все равно будет ходить в сеть. И на него также надо написать тест. Без моков. Репозитории являются одним из примеров таких классов. Я просто не понимаю, почему такие тесты называют интеграционными, а не юнит.
Nex-Otaku
Сообщения: 831
Зарегистрирован: 2016.07.09, 21:07

Re: Юнит-тестирование

Сообщение Nex-Otaku »

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

Это и есть правильный подход. Вот смотри.

1. Допустим, есть класс ReportBuilder, для которого мы пишем юнит-тест.
Этот класс обращается к сети напрямую в нескольких местах собственного кода.

2. Эти сетевые вызовы выносим в отдельный класс, назовём его ReportNetService.

3. Теперь ReportBuilder обращается не напрямую к сети, а вызывает методы ReportNetService. При этом классу ReportBuilder неважно, что внутри ReportNetService, ему нужен лишь результат сетевых вызовов.

4. Далее, мы тестируем только класс ReportBuilder через юнит-тест.

5. В методах ReportNetService можно оставить настоящее обращение к сети, а можно захардкодить какие-то данные, которые сразу будут возвращаться. Тут на наше усмотрение. Если нужна скорость - убираем сетевые вызовы, если по каким-то причинам мы осознанно желаем проверить именно на "живой" сети, оставляем сетевые вызовы. Можно и оба варианта сразу реализовать, подключать по необходимости один или другой.

Суть в том, что когда мы убрали зависимости из тестируемого класса, у нас появился выбор - "замокать" сеть или использовать её. Пока зависимость была в классе ReportBuilder, выбора у нас не было.
sda
Сообщения: 334
Зарегистрирован: 2013.12.19, 09:29

Re: Юнит-тестирование

Сообщение sda »

Да, меня интересует именно тестирование ReportNetService. Чтобы замокать сеть внутри такого класса, придется мокать сами функции языка программирования. Я считаю это неверным. Считаю что при тестировании таких классов необходимо оставлять сеть живой. И еще я не понимаю, почему такие тесты называют интеграционными ? Мы же тестируем юнит. Да, этот юнит осведомлен о сети. Ну и что с того?
Nex-Otaku
Сообщения: 831
Зарегистрирован: 2016.07.09, 21:07

Re: Юнит-тестирование

Сообщение Nex-Otaku »

ReportNetService. Чтобы замокать сеть внутри такого класса, придется мокать сами функции языка программирования
Внутри этого класса мокать ничего не нужно, просто либо
1) используем сеть, либо
2) не используем сеть и сразу возвращаем ожидаемый результат.
меня интересует именно тестирование ReportNetService
Если нужно тестировать не логику "ReportBuilder", а поведение на реальной сети, то ничего не подменяем, и в "ReportNetService" используем обычные сетевые вызовы.
почему такие тесты называют интеграционными ? Мы же тестируем юнит. Да, этот юнит осведомлен о сети. Ну и что с того?
Юнит - это класс либо модуль. В данном случае конкретный класс "ReportBuilder".

Если мы проводим юнит-тестирование класса "ReportBuilder", наш тест отвечает на вопрос "работает ли класс ReportBuilder как задумывалось".

Как правило, нам нужно тестировать только логику внутри этого класса. А это значит, что все его внешние зависимости должны быть неизменными, иначе тест может в любой момент ни с того ни с сего "провалиться". А при повторном прогоне опять "заработать". Такой тест бесполезен. Задача теста - выявить ошибки, при этом мы ищем ошибки логики класса, а ошибки сети нам только мешают.
Ответить