[Перевод] Assert-сообщения в тестах
И снова здравствуйте. В преддверии старта курса «Разработчик C#» перевели интересный материал про assert-сообщения в тестах и с радостью делимся с вами переводом.
В этом посте мы поговорим о том, должны ли вы использовать Assert-сообщения в ваших тестах.
Я получил интересный вопрос от коллеги читателя, на котором хотел бы остановиться поподробнее:
У меня вопрос по поводу Assert-сообщений: следует ли использовать перегрузку, содержащую параметр сообщения, и использовать ее для передачи строки, описывающей причину неудачи Assert (также «Утверждения»)?
Ответ на этот вопрос сводится к двум аспектам:
- Читаемость теста — насколько легко понять, что делает тест.
- Простота диагностики — насколько легко понять, почему тест не пройден.
Давайте обсудим каждый из них в отдельности
Читаемость теста
Люди часто используют Assert-сообщения, чтобы помочь членам команды и самим себе в будущем понять, что происходит в тесте. Давайте рассмотрим следующий пример:
[Test]
public void Hiring_a_new_team_member()
{
var company = new Company();
var person = new Person(UserType.Customer);
company.HireNewMember(person);
Assert.AreEqual(UserType.Employee, person.Type); // нет сообщения
Assert.AreEqual(1, company.NumberOfEmployees); // нет сообщения
}
Вместо голого Assert вы также можете указать причину, по которой тестовый Assert что-либо валидирует:
[Test]
public void Hiring_a_new_team_member()
{
var company = new Company();
var person = new Person(UserType.Customer);
company.HireNewMember(person);
Assert.AreEqual(UserType.Employee, person.Type, "Person must become an employee after hiring");
Assert.AreEqual(1, company.NumberOfEmployees, "Number of employees must increase");
}
Такие утверждения помогают, но они имеют свою цену. Эти сообщения требуют от вас
- Потратить время на их написание
- Поддерживать их продвижение вперед
Здесь набор плюсов и минусов такой же, как и в комментариях к коду. И, как и в случае с комментариями, я советую вам: не пишите assert-сообщения чисто в целях читаемости теста. Если вы чувствуете, что тест не очевиден без assert-сообщений, попробуйте вместо этого провести его рефакторинг. В долгосрочной перспективе легче сделать тест говорящим самим за себя, нежели поддерживать его синхронизацию с assert-сообщениями (и это только вопрос времени, когда синхронизация будет нарушена).
Вводите assert-сообщения только тогда, когда это абсолютно необходимо — когда вы не можете улучшить читаемость теста каким-либо другим способом. Но даже тогда, склоняйтесь к выбору не писать их.
Самый простой способ получить быстрый выигрыш в читаемости теста — это переключиться на удобочитаемую запись утверждений. Например, NUnit имеет специальную модель утверждений на основе ограничений (constraint), которая помогает вам писать свои утверждения следующим образом:
[Test]
public void Hiring_a_new_team_member()
{
var company = new Company();
var person = new Person(UserType.Customer);
company.HireNewMember(person);
Assert.That(person.Type, Is.EqualTo(UserType.Employee));
Assert.That(company.NumberOfEmployees, Is.EqualTo(1));
}
Или вы можете использовать мои любимые Fluent Assertions:
[Test]
public void Hiring_a_new_team_member()
{
var company = new Company();
var person = new Person(UserType.Customer);
company.HireNewMember(person);
person.Type.Should().Be(UserType.Employee);
company.NumberOfEmployees.Should().Be(1);
}
Такие утверждения читаются на простом английском языке — именно так, как вы хотели бы, чтобы читался весь ваш код. Мы, люди, предпочитаем воспринимать информацию в форме историй. Все истории придерживаются данной модели:
[Субъект] [действие] [объект].
Например,
Боб открыл дверь.
Здесь Боб
— субъект, открыл
— действие, а дверь
— объект. То же самое относится и к коду.
Эта версия
company.NumberOfEmployees.Should().Be(1);
читает лучше чем
Assert.AreEqual(1, company.NumberOfEmployees);
именно потому, что здесь прослеживается история.
Кстати, парадигма ООП стала успешной отчасти благодаря удобству чтения. С ООП вы также можете структурировать код так, чтобы он читался как история.
Простота диагностики
Другой взгляд на assert-сообщения — с точки зрения простоты диагностики. Другими словами, простота понимания причины провала теста без изучения кода этого теста. Это полезно при чтении результатов сборки CI.
С точки зрения диагностики, следуйте данному руководству: если вы можете легко повторно запустить тест локально, этот тест не нуждается в assert-сообщении. Это верно для всех модульных тестов (поскольку они не работают с внепроцессными зависимостями), но в меньшей степени для интеграционных и сквозных тестов.
Пирамида тестирования
По мере того, как вы будете подниматься выше в тестовой пирамиде, вам может понадобиться более подробная информация, потому что интеграционные (и особенно сквозные) тесты медленнее и возможно вы не сможете их повторно запускать по желанию.
Но даже с интеграцией и сквозными тестами существуют способы облегчить диагностику, не прибегая к assert-сообщениям:
- Сделайте так, чтобы тест проверял один модуль поведения — когда тест проверяет что-то одно, часто легко определить, что пошло не так. (Это не всегда применимо к сквозным тестам, так как вы можете захотеть, чтобы такие тесты проверяли, как несколько единиц поведения работают вплотную).
- Именуйте свои модульные тесты соответственно — Идеальное имя теста описывает поведение приложения в бизнес-терминах, так что даже непрограммист может его понять.
И не забывайте, что даже без пользовательских утверждений у вас все еще есть сообщения, которые генерирует для вас среда модульного тестирования.
Например, ошибка в следующем утверждении:
person.Type.Should().Be(UserType.Employee);
выдает следующее сообщение об ошибке:
Xunit.Sdk.EqualException: Assert.Equal() Failure
Expected: Employee
Actual: Customer
Комбинация таких генерируемых средой сообщений и понятных человеку имен тестов делает 90% пользовательских assert-сообщений бесполезными даже с точки зрения простоты диагностики. Единственное исключение — это длительные сквозные тесты. Они часто содержат многоэтапные проверки, поэтому имеет смысл использовать дополнительные assert-сообщения, чтобы понять, какой из шагов не удался. Однако таких сквозных тестов не должно быть много.
Конечно, чтобы воспользоваться сгенерированными фреймворком сообщениями о сбоях, вам нужно избегать общих булевых сравнений, таких как:
(person.Type == UserType.Employee).Should().BeTrue();
Потому что они приводят к следующему сообщению об ошибке:
Xunit.Sdk.TrueException: Assert.True() Failure
Expected: True
Actual: False
Что не помогает с диагностикой вообще.
Резюме
Вы часто ощущаете лень, когда речь идет о написании утверждений? Что ж, теперь вы можете оправдать ее и отослать своих коллег к этой статье.
«Я рад, что у этого есть название»
Шутки в сторону, однако, вот резюме:
- Существует два аспекта использования assert-сообщений:
- Читаемость теста (насколько легко понять, что делает тест).
- Простота диагностики (насколько легко понять, почему тест не проходится во время сборки CI).
- С точки зрения читабельности теста assert-сообщения являются комментариями кода. Вместо того чтобы полагаться на них, займитесь рефакторингом теста для достижения читаемости.
- С точки зрения простоты диагностики, лучшая альтернатива assert-сообщениям:
- Проверка тестом одного модуля поведения
- Именование тестов в бизнес терминах
- Единственное исключение — длительные сквозные тесты.
На этом все. Узнать подробнее о нашем курсе модно на бесплатном вебинаре, который пройдет уже сегодня.