Модульное тестирование — следует ли / как мне писать тесты для покрытия нового кода, который не влияет на интерфейс метода?

Модульное тестирование — следует ли / как мне писать тесты для покрытия нового кода, который не влияет на интерфейс метода?
Модульное тестирование — следует ли / как мне писать тесты для покрытия нового кода, который не влияет на интерфейс метода? - cdc @ Unsplash

Иногда подробные модульные тесты не являются подходящим инструментом для работы

Вот копия метода, о котором мы говорим:

public Task SendUserEmail(long userId, string subject, string message)
{
    var user = _db.GetUser(userId);

    var emailMessage = new EmailMessage {
        Recipient = user.Email,
        Subject = subject,
        Message = message
    }

    _emailService.SendEmail(emailMessage);

    _db.LogEmailSend(emailMessage);
}

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

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

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

Для меня все это вместе предполагает, что эту функцию, вероятно, следует протестировать с помощью интеграционного теста.

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

В стиле Gherkin/BDD я ожидаю, что тест для этого будет выглядеть так:

Сценарий: отправка и регистрация сообщений электронной почты пользователям

Дан пользователь 123 с электронной почтой [email protected]
И что время 2022-12-30 21:02:54

Когда я отправляю пользователю 123 электронное письмо с темой «испытательная тема» и текстом «нажмите здесь, чтобы собрать 1 миллион»

Затем

  • есть электронная почта, которая выглядит как

    From: "This Company" <[email protected]>
    To: <[email protected]>
    Date: Thu, 30 Dec 2022 21:02:54 +0100
    Subject: test subject
    
    click here to collect 1 million
    
    (compliance footer)
    
  • на 2022-12-30 21:02:54 есть запись в журнале электронной почты для отправки сообщения на [email protected]

Здесь происходят две важные вещи, одна практическая, другая концептуальная:

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

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

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

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


Заметка о том, как следовать советам Роберта С. Мартина. Этот автор часто имеет хорошие идеи, но выражает их вводящим в заблуждение абсолютным образом. Утверждение, что «вы должны написать неудачный тест, прежде чем писать какой-либо производственный код», очень сильное и абсолютное, с такими словами, как «должен» и «любой».

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

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

Говоря более разумно, его совет можно было бы интерпретировать следующим образом:

Тесты имеют ценность. Написание тестов особенно важно для производственного кода. Может быть полезен подход «сначала тестирование», такой как TDD или некоторые методы BDD.

Рекомендую посмотреть эти видео для лучшего погружения в вопрос:

Прикрепленное видео 1 - Андрей Глазков — Тестирование систем с внешними зависимостями: проблемы, решения, Mountebank

Прикрепленное видео 2 - Илья Казначеев - Готовим юнит-тесты в ABAP


LetsCodeIt, 19 января 2023 г., 02:10