Как стать автором
Обновить

Комментарии 8

а как обстоит дело с логированием сгенерированных параметров? Чтобы можно было в будущем воспроизвести падение теста

как и прежде, всё логгируется - это продемонстрировано в скриншотах в статье

я имел виду не только значения expected & actual в тексте ошибки. А и остальные сгенерированные переменные, которые могут быть задействованы в тесте. Т.е. логирование всего нагенерированного

Решаю похожую проблему, через инициализацию happy path в конструкторе. Далее набрасываю в Arrange через _mock.Setup() разные кейсы для проверок. Тем самым тесты получаются детерминированными и с небольшой секцией Arrange

Очень удобный подход, тоже к этому пришли.

Я полагаю, что ни у кого не вызывает сомнений необходимость писать юнит тесты.

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

Но в случае библиотек - где пользователи (прикладные программисты) действительно используют отдельные модули, а потому, код, проверяющий эти модули фактически по отдельности, писать все равно надо, смысл писать модульные тесты таки есть - потому что они удобно запускаются из IDE. Поэтому я для своей библиотеки ActiveSession их написал.

По сути, при написании тестов узкое место одно — это Arrange этап, поскольку Act и Assert в среднем занимают 2 строчки. А вот Arrange самый интересный и объёмный. Подготовка объектов, коллекций, настройка интеграций, конфигураций — всё там.

Мой ответ на это - создание TestSetup-класса. В его конструкторе производится вся эта нудная подготовка, которую нужно делать в тестоовых методов много раз. А в тестовых методах просто создаются экземпляры этого класса, при необходимости - подстраиваются их свойства и потом уже выполняются шаги Act/Assert. А ещё, если для одного модуля есть группы схожих тестов (например, несколько тестов одного метода для разных путей его выполнения) удобно использовать наследование: создать производный TestSetup-класс, унаследованный от базового? и сделать в его конструкторе дополнительную настройку для этой группы тестов. Примеры, как я это делаю, можно найти в репозитории по ссылке, в тестовом проекте ActiveSessions.Tests.

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

Поговорим о решении.

Используем настройку Arrange по happy path, т.е. на идеальный сценарий, прям в конструкторе теста. Таким образом основной успешный тест будет самым простым, буквально в пару строк.

А вот все отклонения вносятся уже в настроенный идеальный сценарий, в код каждого теста. Добавляется ещё несколько строк для каждого отклонения.

Это позволяет всегда оценивать все тесты с учётом изменений в идеальном сценарии, отказаться от кучи бойлерплейта, просто вносим точечные мутации в Arrange и проверяем ровно то, что нам интересно.

К каждому из трех представленных "абстрагированных от рутины" тестов есть вопросы:

Handle_HappyPath_DoesNotThrow

Все зависимости класса подменены. В какой ситуации этот метод может бросить исключение? Я вижу 2 варианта:

  • Ошибка валидации входных данных - см. ниже.

  • NRE от мока. Следовательно, тест проверяет не бизнес-логику, а вашу реализацию моков, насколько качественно они имитируют поведение сервисов в сценарии happy path. Что не имеет практической ценности.

await Assert.ThrowsAsync<SomeException>(
	() => handler.Handle(request with { Field1 = -1 }, ct: default));

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

response.ExternalDataCollection.Should().BeEmpty();

Что проверяет этот тест, если провайдер данных замокан?

Зарегистрируйтесь на Хабре, чтобы оставить комментарий