Введение

Часто говорят, что качество кода, который вы пишете для своих тестов, должно быть не ниже качества рабочего кода.

Хотя я согласен с приведенным выше утверждением, я считаю, что стили программирования и шаблоныдля тестовдолжны отличаться от рабочего кода.

Почему? Потому что мы ожидаем разных результатов от тестов и производственного кода.

  • Мы хотим, чтобы тесты были необъяснимыми. Если тест терпит неудачу, мы не хотим проходить долгий путь, чтобы понять его. Обычно мы заняты чем-то другим.
  • Мы ненавидим тесты с неудачным каскадом. Возможно, вы испытали что-то вроде этого:

Я только что добавил аргумент X к методу Y в классе Z. Я понимаю, почему эти 2 теста терпят неудачу. Тем не менее, я потрачу следующие 3 часа на исправление этих 47 неудачных тестов, хотя мое изменение даже не связано с ними.

  • Мы хотим/должны свести к минимуму нагрузку на обслуживание тестов, чтобы наши тесты продолжали работать. Существует сильное давление на тесты, потому что они не нравятся пользователям. В конечном счете, мы не хотим, чтобы кто-то (или мы сами) спрашивали: «Зачем нам вообще писать тесты?»

Разработчики тратят сотни часов на написание модульных тестов, потому что их ценность доказана годами. Авторы одобряют деятельность. В наши дни TDD и высокий процент покрытия кода являются обычным явлением. Однако чаще всего разработчики заканчивают тем, что пишут огромную кучу неподдерживаемой чепухи.

Как лучше писать тесты

Есть несколько шаблонов, которые могут сделать ваши тесты удобными в сопровождении. Очень рекомендую прочитать Эффективная работа с модульными тестами Джея Филдса. Вот краткое изложение некоторых идей, обсуждаемых в книге:

Не используйте циклы в тестовом коде

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

Настройка в режиме реального времени

СУХОЙ это хорошо. Да, но лучше понять тест с первого взгляда.

В каждом тесте отдавайте предпочтение настройке в режиме реального времени, а не методу настройки.

Использование общего метода настройки обычно приводит к таким проблемам, как:

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

Если вы считаете, что ваша логика инициализации становится слишком сложной, см. следующий раздел.

Построитель данных

Если вы создаете несколько экземпляров тестируемого класса и его зависимости достаточно просты, простоиспользуйте простое «новое» для их создания и инициализации.

Если вы обнаружите, что пишете сложную логику для создания объектов, возможно, стоит создать Data Builder. Подробности смотрите в этом посте.

Утвердить последним

Возможно, вы уже знакомы с AAA (Arrange-Act-Assert). Последняя А оказывается самой важной. Даже если вы не можете перейти на полный ААА, ваши тесты будут более читаемыми и удобными в сопровождении, если вы примете шаблон Asert Lasts.

Утверждение бросков

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

Некоторые платформы поддерживают декораторы ExpectedException, которые можно применять к методам тестирования:

…но это нарушает правило Assert Last.

В идеале нам нужно что-то вроде:

Если выбранный вами фреймворк не поддерживает его, вы можете без особых усилий реализовать Assert.Throws самостоятельно. "[Пример]"

Ожидать литералы

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

Ожидаемое значение должно быть самим литералом, а не переменной, содержащей значение, которое ранее было создано литералом.

Избегайте чрезмерной спецификации

Старайтесь, чтобы ваши тесты были как можно более простыми. Они должны быть ориентированы на одну функциональность. Это нормально иметь несколько тестов, которые взаимодействуют с несколькими объектами, но их должно быть ограниченное количество в наборе тестов. Большинство ваших тестов должны быть очень простыми и взаимодействовать с одним объектом. Закон Деметры действует и здесь.

ROI

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

Стремление к высокому покрытию кода важно, но 100% нецелесообразно. Если вы достигаете 100% покрытия кода, среди прочего это означает, что вы, возможно, написали много тестов с отрицательной окупаемостью инвестиций.

Вывод

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