Комментарии 8
а как обстоит дело с логированием сгенерированных параметров? Чтобы можно было в будущем воспроизвести падение теста
Решаю похожую проблему, через инициализацию 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();
Что проверяет этот тест, если провайдер данных замокан?
Искусство Unit-тестирования: сокращаем Arrange до нуля