Вадим Гагаринский

Специалист по тестированию в ГК Юзтех

Проверки в автотестах являются обязательным компонентом, так как основная задача любого теста сравнить ожидаемый результат с фактическим. Меня зовут Вадим, я специалист по тестированию, и в этой статье я хочу уделить внимание одной из частей любого автотеста – Assert. Казалось бы, какие трудности могут возникнуть с этим, на первый взгляд, простым компонентом любого автотеста? На одном из своих проектов я столкнулся с большим количеством автотестов, проблемой которых как раз и были неверно написанные проверки. Хочу рассказать о причинах возникновения этих трудностей и поделиться путём решения проблемы, который мне удалось пройти вместе с командой. 

Проблемы автотестов

Я подключился к проекту, когда 12000 автотестов уже были написаны, а ещё несколько тысяч ожидали своего написания. Команда Auto QA погрязла в изучении логов упавших тестов в CI/CD. Основной проблемой, почему на это уходила масса времени, была неинформативность ошибок. Использовалась стандартная библиотека Assert’ов, и тест, написанный с её использованием, выдавал ошибку, по которой было очень тяжело диагностировать что-либо, не заглядывая в код.

Пример теста:

Пример ошибки:

Сперва было принято неверное решение, которое помогло решить данную проблему, но породило другую. Мы начали добавлять сообщения в Assert’ы.

Пример кода:

Пример ошибки:

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

Плюсы библиотеки Fluent Assertions

Решить эти проблемы нам помогла библиотека Fluent Assertions. Основным плюсом библиотеки является простота её использования: формат написания проверки субъект -> действие -> объект более привычен для людей. Другим плюсом является информативность сообщений, который встроены по умолчанию.

Наш старый пример, но с использованием Fluent Assertions, строка 26:

Пример ошибки:

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

Функционал библиотеки

К базовому функционалу, который выполняет стандартные функции, можно отнести следующие Assert’ы:

Стоит отметить, что большинство проверок в библиотеке имеют свою противоположность со словом Not в начале. 

На первых этапах перевода проекта на Fluent Assertions я вместе с командой заменял Assert’ы из стандартной библиотеки на эти базовые проверки. 

Особое внимание хочется обратить на конструкцию AssertionScope. Эта конструкция позволяет объединить несколько проверок в 1 блок, который осуществит их одновременное выполнение. Почему этой конструкции уделено особое внимание в этой статье?

Время выполнения 1 автотеста на проекте варьировалось от 5 до 15 минут. При проверке упавшего автотеста хочется убедиться, что не только причина падения была исправлена, но и последующие проверки в том же тесте проходят без проблем. Без использования этой конструкции алгоритм работы с упавшим тестом выглядит так:

  1. Исправление проблемы, из-за которой тест падает;

  2. Тестовый запуск с возможным нахождением другой проблемы в последующих проверках;

  3. Если проблема далее найдена, то возвращаемся к пункту 1.

Таким образом ситуация с N проверок может растянуть исправление автотеста до N*t, где t — время запуска этого теста. Именно здесь на помощь приходит AssertionScope.

Пример использования AssertionScope:

Пример ошибок:

Как мы видим, после падения теста на первой проверке, тест проверил и вторую конструкцию и обнаружил, что там тоже возникает проблема.

Отдельное внимание я уделю измерению времени, которое позволяет выполнить данная библиотека. 

Пример:

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

Пример сравнения даты, записанной в переменную DateTime с использованием конструктора (34 строка), и даты, записанной с использованием Fluent Assertions (37 строка):

Конечно, библиотека позволяет не просто сравнивать даты, но и определять разницу между ними:

Очень просто начать работать с этой библиотекой. Для этого её нужно просто установить через NuGet Package Manager, а потом в нужном классе прописать using FluentAssertions;

Немного о цифрах

Для интеграции этого решения в проект и замены старого функционала на Fluent Assertions в проблемных тестах у нас, команды из 4 человек, ушло 2 недели. В следующие полтора месяца внедрение библиотеки коснулось оставшихся тестов. Менее чем через квартал мы вышли на написание новых тестов и расширения нашего тестового проекта. Сейчас, придя на новый проект с гораздо меньшим количеством тестов, я сразу предложил использовать Fluent Assertions и очень быстро обновил все тесты. 

Заключение

1) Fluent Assertions позволяют читать код как книгу на английском языке. В большинстве случаев не нужны громоздкие, трудно поддерживаемые сообщения.

2) Fluent Assertions позволяют более просто диагностировать проблему прямо при чтении результатов в CI. Это очень полезно при масштабных тестах, запуск которых лишний раз потратит кучу времени тестировщика.

3) При составлении новых автотестов Fluent Assertions позволяют с лёгкостью использовать уже написанные куски кода, т. к. не составляет труда лишний раз понять, что там написано.

Спасибо за то, что уделили время прочтению статьи. В комментариях можете поделиться своим опытом использования Fluent Assertions.

Комментарии (4)


  1. mylitsyn
    03.10.2022 08:54
    +1

    Что у вас со скриншотами? Это же ужас какой то)


  1. AxisPod
    03.10.2022 10:35

    Вот только Constraint Assertion из NUnit куда более юзабельны. Fluent Assertion способна описать assertion как входной параметр теста? А вот NUnit позволяет это сделать довольно красиво.


    1. vabka
      03.10.2022 11:05

      Можно пример, когда это нужно?


  1. Melanchall
    03.10.2022 12:39

    Наверное, полезность Fluent Assertions разная в зависимости от типа тестов. Объясню.

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

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

    Хорошо, добавим сообщение. А вот тут самый ад Fluent Assertions. Потому что постоянно нужно помнить шаблон, который использует библиотека для формирования финального сообщения. Что писать в ассерт: как должно быть или что неправильного случилось? Эта неопределённость порождает временами безумные сообщения.

    В случае же со стандартными ассертами, что в MSTest, что в NUnit при наличии сообщений решаются обе проблемы:

    1. сообщение об ошибке будет гарантированно то, какое мы напишем (ну за исключением Expected/Actual);

    2. можно всегда скопировать сообщение, вставить в IDE и найти проблемное место.