Автоматизация и пирамида тестов

11-11-2019

Понятие «Пирамида тестов» помогает:

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

Важность автоматизации (тестов)

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

Пирамида тестов

Если серьёзно подходить к автоматическим тестам, то есть одна ключевая концепция: пирамида тестов. Её представил Майк Кон в своей книге «Scrum: гибкая разработка ПО» (Succeeding With Agile. Software Development Using Scrum). Это отличная визуальная метафора, наталкивающая на мысль о разных уровнях тестов. Она также показывает объём тестов на каждом уровне.

1

Оригинальная пирамида тестов Майка Кона состоит из трёх уровней (снизу вверх):

  • Юнит-тесты.
  • Сервисные тесты (API тестирование).
  • Тесты пользовательского интерфейса.

При этом:

  • скорость выполнения тестов уменьшается снизу вверх (от юнит - до тестов пользовательского интерфейса)
  • изолированность тестируемых объектов уменьшается снизу вверх (от юнит - до тестов пользовательского интерфейса)

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

  • Писать тесты разной детализации.
  • Чем выше уровень, тем меньше тестов.

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

Юнит-тесты

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

Что такое юнит?

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

Общительные и одинокие тесты

Некоторые утверждают, что всех участников (например, вызываемые классы) тестируемого субъекта следует заменить на имитации (mocks) или заглушки (stubs), чтобы создать идеальную изоляцию, избежать побочных эффектов и сложной настройки теста. Другие утверждают, что на имитации и заглушки следует заменять только участников, которые замедляют тест или проявляют сильные побочные эффекты (например, классы с доступом к БД или сетевыми вызовами).

Иногда эти два вида юнит-тестов называют одинокими (solitary) в случае тотального применения имитаций и заглушек или общительными (sociable) в случае реальных коммуникаций с другими участниками Но в итоге не имеет значения, какой тип тестов Вы выберете. Что реально важно, так это их автоматизация. Часто используют оба подхода. Если неудобно работать с реальными участниками, используют имитации и заглушки. Если привлечение реального участника даёт больше уверенности в тесте, то заглушают только самые дальние части сервиса.

Имитации и заглушки

Имитации (mocks) и заглушки (stubs) — это два разных типа тестовых дублёров (вообще их больше). Многие используют термины взаимозаменяемо. К объектам из продакшена тестовые дублёры создают реализацию для тестов.

Проще говоря, Вы заменяете реальную вещь (например, класс, модуль или функцию) поддельной копией. Подделка выглядит и действует как оригинал (даёт такие же ответы на те же вызовы методов), но это заранее установленные ответы, которые Вы сами определяете для юнит-теста.

Тестовые дублёры используются не только в юнит-тестах. Более сложные дублёры применяются для контролируемой имитации целых частей вашей системы. Однако в юнит-тестах используется особенно много имитаций и заглушек (в зависимости от того, предпочитаете Вы общительные или одиночные тесты) просто потому что множество современных языков и библиотек позволяют легко и удобно их создавать.

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

Ваши юнит-тесты будут работать очень быстро. На приличной машине можно прогнать тысячи модульных тестов за нескольких минут. Тестируйте изолированно небольшие фрагменты кодовой базы и избегайте контактов с БД, файловой системой и HTTP-запросов (ставя здесь имитации и заглушки), чтобы сохранить высокую скорость.

Поняв основы, со временем Вы начнёте всё более свободно и легко писать юнит-тесты. Заглушка внешних участников, настройка входных данных, вызов тестируемого субъекта — и проверка, что возвращаемое значение соответствует ожидаемому. Посмотрите на разработку через тестирование (TDD), этот подход может помочь создать хорошую поддерживаемую архитектуру, автоматически выдавая всеобъемлющий и полностью автоматизированный набор тестов.

Что тестировать?

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

Юнит-тест должен как минимум протестировать открытый интерфейс класса. Закрытые методы всё равно нельзя протестировать, потому что их нельзя вызвать из другого тестового класса. Защищённые или доступные лишь в пределах пакета (package-private) методы доступны из тестового класса (учитывая, что структура пакета тестового класса такая же, как на продакшне), но тестирование этих методов может уже зайти слишком далеко.

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

Почему так?

Тесты, слишком привязанные к коду продакшна, быстро начинают раздражать. Как только Вы осуществляете рефакторинг кода (то есть изменяете внутреннюю структуру кода без изменения внешнего поведения), модульные тесты сразу ломаются.

Таким образом, Вы теряете важное преимущество юнит-тестов: действовать в качестве системы безопасности для изменений кода.

Что же делать? Не отражайте в модульных тестах внутреннюю структуру кода. Тестируйте наблюдаемое поведение.

Например:

если я введу значения x и y, будет ли результат z?

вместо этого:

если я введу x и y, то обратится ли метод сначала к классу А, затем к классу Б, а затем сложит результаты от класса А и класса Б?

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

Вы должны протестировать публичный интерфейс. Но ещё более важно не тестировать тривиальный код. Вы ничего не получите от тестирования простых геттеров или сеттеров или других тривиальных реализаций (например, без какой-либо условной логики).

Cтруктура теста

Хорошая структура всех ваших тестов (не только модульных) такова:

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

Есть хорошая мнемоника для запоминания этой структуры: три A (Arrange, Act, Assert). Можно использовать и другую мнемонику с корнями в BDD (разработка, основанная на описании поведения). Это триада дано, когда, тогда, где «дано» отражает настройку, «когда» — вызов метода, а «тогда» — утверждение.

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

Интеграционные тесты

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

Для автоматизированных тестов это означает, что нужно запустить не только собственное приложение, но и интегрируемый компонент. Если Вы тестируете интеграцию с БД, то при выполнении тестов надо запустить БД. Чтобы проверить чтение файлов с диска нужно сохранить файл на диск и загрузить его в интеграционный тест. Вместе с контрактным тестированием и выполнением контрактных тестов на дублёрах и реальных реализациях можно придумать интеграционные тесты, которые быстрее, более независимы и обычно проще в понимании.

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

Тест интеграции БД выглядит следующим образом:

  • Запуск базы данных.
  • Подключение приложения к БД.
  • Запуск функции в коде, которая записывает данные в БД.
  • Проверка, что ожидаемые данные записаны в базу путём их чтения из БД.

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

Если нет возможности локально запустить стороннюю службу, то лучше запустить выделенный тестовый инстанс и указать на него в интеграционным тесте. В автоматизированных тестах избегайте интеграции с реальной системой продакшна. Интеграция с сервисом по сети — типичное свойство широкого интеграционного теста. Обычно из-за неё тесты труднее писать и они медленнее работают.

Что касается пирамиды тестов, то интеграционные тесты находятся на более высоком уровне, чем модульные. Интеграция файловых систем и БД обычно гораздо медленнее, чем выполнение юнит-тестов с их имитациями. Их также труднее писать, чем маленькие изолированные модульные тесты. В конце концов, нужно думать о работе внешней части теста. Тем не менее, они имеют преимущество, потому что дают уверенность в правильной работе приложения со всеми внешними частями, с какими нужно. Юнит-тесты тут бесполезны.

Тесты UI

У большинства приложений есть какой-то пользовательский интерфейс. Обычно мы говорим о веб-интерфейсе в контексте веб-приложений.

Тесты UI проверяют правильность работы пользовательского интерфейса приложения. Действия пользователя должны инициировать правильные события, данные должны представляться пользователю, состояние UI должно изменяться ожидаемым образом. Иногда говорят, что тесты UI и сквозные тесты — это одно и то же.

Да, тестирование приложения от начала до конца часто означает прохождение через пользовательский интерфейс. Но обратное неверно.

Тестирование пользовательского интерфейса необязательно должно проводиться в сквозном режиме. В зависимости от используемой технологии, тестирование UI может оказаться таким же простым, как написание некоторых модульных тестов для фронтенда JavaScript с заглушенным бэкендом.

Для тестирования UI традиционных веб-приложений предназначены специальные инструменты вроде Selenium. Если Вы считаете пользовательским интерфейсом REST API, то достаточно правильных интеграционных тестов вокруг API.

В веб-интерфейсах желательно проверить несколько аспектов UI, в том числе поведение, вёрстка, юзабилити, соблюдение фирменного стиля и др.

К счастью, тестирование поведения UI довольно простое. Щёлкаете здесь, вводите данные там — и проверяете, что состояние UI меняется соответствующим образом. Современные фреймворки для одностраничных приложений (react, vue.js, Angular и прочие) часто поставляются с инструментами и хелперами для тщательного тестирования этих взаимодействий на довольно низком уровне (в юнит-тесте). Даже если выкатить собственную реализацию фронтенда на ванильном JavaScript, всё равно можно использовать обычные инструменты тестирования, такие как Jasmine и Mocha. Для более традиционного приложения с рендерингом на стороне сервера наилучшим выбором станут тесты на основе Selenium.

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

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

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

Сквозные тесты

Тестирование развёрнутого приложения через UI — это самый полный тест, какой только можно провести. Описанные выше тесты UI через WebDriver — хорошие примеры сквозных тестов. Сквозные тесты (также называемые тестами широкого стека) дают максимальную уверенность, работает программное обеспечение или нет. Selenium и протокол WebDriver позволяют автоматизировать тесты, автоматически отправляя headless-браузер на развёрнутые сервисы для выполнения кликов, ввода данных и проверки состояния UI.

У сквозных тесты другие проблемы. Они известны своей ненадёжностью, сбоями по неожиданным и непредвиденным причинам. Довольно часто это ложноположительные сбои. Чем более сложный UI, тем более хрупкими становятся тесты. Причуды браузера, проблемы с синхронизацией, анимация и неожиданные всплывающие диалоги — лишь некоторые из причин, которые могут отнять у Вас много времени на отладку тестов. Подумайте о самых главных взаимодействиях пользователей с приложением. Придумайте главные «маршруты» пользователей от экрана к экрану, чтобы автоматизировать самые важные из этих шагов в сквозных тестах.

Если Вы делаете интернет-магазин, то самым ценным «маршрутом» будет поиск продукта — помещение его в корзину — оформление заказа. Вот и всё. Пока этот маршрут работает, нет особых проблем. Возможно, Вы найдёте еще пару важных маршрутов для сквозных тестов. Всё остальное, вероятно, принесёт больше проблем, чем пользы.

Помните: в вашей пирамиде тестов много низкоуровневых тестов, где мы уже протестировали все варианты пограничных ситуаций и интеграции с другими частями системы. Нет необходимости повторять эти тесты на более высоком уровне. Большие усилия по техническому обслуживанию и много ложных срабатываний слишком замедлит вашу работу, а рано или поздно лишит вас доверия к тестам вообще. Для сквозных тестов многие разработчики выбирают Selenium и протокол WebDriver. С Selenium можете выбрать любой браузер и натравить его на сайт. Пусть нажимает повсюду кнопки и ссылки, вводит данные и проверяет изменения в UI.

К Selenium нужен браузер, который можно запустить и использовать для тестов. Есть несколько так называемых «драйверов» к разным браузерам. Какой бы браузер Вы ни выбрали, следует убедиться, что у всех разработчиков и на сервере CI установлена правильная версия браузера. Запуск полноценного браузера в тестовом наборе может стать проблемой. Особенно если сервер непрерывной доставки, где работает наш конвейер, не способен развернуть браузер с UI (например, потому что X-Server недоступен). В этом случае можно запустить виртуальный X-Server вроде xvfb.

Более новый подход заключается в использовании headless-браузера (т.е. браузера без пользовательского интерфейса) для тестов WebDriver. До недавнего времени чаще всего для автоматизации браузерных задач использовался PhantomJS. Но когда Chromium и Firefox внедрили headless-режим из коробки, PhantomJS внезапно устарел. В конце концов, лучше протестировать сайт с помощью реального браузера, который действительно есть у пользователей (например, Firefox и Chrome), а не с помощью искусственного браузера только потому что это удобно вам как разработчику.

Приёмочные тесты — ваши фичи правильно работают?

Чем выше Вы поднимаетесь в пирамиде тестов, тем больше вероятность возникновения вопросов: правильно ли работают фичи с точки зрения пользователя. Вы можете рассматривать приложение как чёрный ящик и изменить направленность тестов от прошлого:

когда я ввожу x и y, возвращаемое значение должно быть z

к следующему:

учитывая, что есть авторизованный пользователь

и есть изделие «велосипед»

когда пользователь переходит на страницу описания изделия «велосипед»

и нажимает кнопку «Добавить в корзину»

тогда изделие «велосипед» должно быть в его корзине

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

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

Можно ещё упомянуть BDD (разработка, основанная на описании поведения) и инструменты для нее. BDD и соответствующий стиль написания тестов — хороший трюк, чтобы изменить своё мышление от деталей реализации к потребностям пользователей.

Вам даже не обязательно внедрять полномасштабные инструменты BDD, такие как Cucumber. Некоторые assertion-библиотеки вроде chai.js позволяют писать утверждения (assertions) с ключевыми словами в стиле should, которые приближают тесты к BDD.

Исследовательское тестирование

Даже самые прилежные усилия по автоматизации тестов не идеальны. Иногда в автоматических тестах Вы пропускаете определённые пограничные случаи. Иногда просто невозможно обнаружить определённую ошибку, написав модульный тест. Некоторые проблемы качества вообще не проявятся в автоматизированных тестах (подумайте о дизайне или юзабилити). Несмотря на самые лучшие намерения в отношении автоматизации тестов, ручные тесты в некоторых отношениях по-прежнему незаменимы. Включите исследовательские тесты в свой набор тестов. Эта процедура тестирования вручную подчёркивает свободу и творческие способности тестировщика, который способен найти проблемы качества в работающей системе. Просто выделите немного времени в расписании, засучите рукава и постарайтесь вызвать сбой приложения каким-нибудь способом. Включите деструктивное мышление и придумывайте способы, как спровоцировать проблемы и ошибки в программе. Документируйте всё, что найдёте. Ищите баги, проблемы дизайна, медленное время отклика, отсутствующие или вводящие в заблуждение сообщения об ошибках и всё остальное, что вас раздражает как пользователя.

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

Во время исследовательского тестирования Вы обнаружите проблемы, которые незаметно проскользнули через конвейер сборки. Не расстраивайтесь. Это хорошая обратная связь для улучшения конвейера сборки. Как и с любой обратной связью, обязательно отреагируйте со своей стороны: подумайте, какие действия предпринять, чтобы избежать такого рода проблем в будущем. Может, Вы пропустили определённый набор автоматических тестов. Возможно, были небрежны с автоматизированными тестами на этом этапе и следует более тщательно проводить тесты в будущем. Возможно, есть какой-то блестящий новый инструмент или подход, который можно использовать в своем конвейере, чтобы избежать таких проблем. Обязательно отреагируйте так, чтобы ваш конвейер и вся система поставки программного обеспечения стали лучше и совершенствовались с каждым шагом.

Путаница с терминологией в тестировании

Всегда сложно говорить о разных классификациях тестов. Представленное здесь понимание юнит-тестов (модульных тестов) может слегка отличаться от вашего. С интеграционными тестами ещё хуже. Для некоторых людей интеграционное тестирование — это очень широкая деятельность, которая тестирует множество различных частей всей системы. Для некоторых это довольно узкая вещь: тестирование только интеграции с одной внешней частью за раз. Некоторые называют это интеграционными тестами, некоторые — компонентными, другие предпочитают термин сервисный тест. Кто-то заявит, что это вообще три совершенно разные вещи. Нет правильного или неправильного определения. Сообщество разработчиков ПО просто не установило чётко определённых терминов в тестировании. Важно воспринимать это так: Вы просто находите термины, которые работают для вас и вашей команды. Ясно определите для себя различные типы тестов, которые хотите написать. Согласуйте термины в своей команде и найдите консенсус относительно охвата каждого типа теста. Если в своей команде (или даже во всей организации) Вы будете последовательны с этими терминами, то это всё, о чем нужно позаботиться. Думаю, это прекрасная демонстрация, что не стоит слишком зацикливаться на названиях и конвенциях по терминам. Если же Вы всё-таки пожелаете определиться с терминологией, то International Software Testing Qualifications Board® (ISTQB®) является рекомендованным ресурсом для этого.

Внедрение тестов в конвейер развёртывания

Если Вы используете непрерывную интеграцию или непрерывную доставку, то ваш конвейер развёртывания запускает автоматические тесты каждый раз при внесении изменений в ПО. Обычно конвейер разделяется на несколько этапов, которые постепенно дают всё больше уверенности, что ваша программа готова к развёртыванию в рабочей среде. Услышав обо всех разновидностях тестов, возможно, Вы задаётесь вопросом, как поместить их в конвейер развёртывания. Чтобы ответить на это, следует просто подумать об одной из самых основополагающих ценностей непрерывной доставки (это одна из ключевых ценностей экстремального программирования и гибкой разработки): о быстрой обратной связи.

Хороший конвейер сборки максимально быстро сообщает об ошибках. Вы не хотите ждать целый час, чтобы узнать, что последнее изменение сломало некоторые простые модульные тесты. Если конвейер работает так медленно, то Вы могли уже уйти домой, когда поступила обратная связь. Информация должна приходить в течение нескольких секунд или нескольких минут с быстрых тестов на ранних этапах конвейера. И наоборот, более длительные тесты — обычно с более широкой областью — размещаются на более поздних этапах, чтобы не тормозить фидбек от быстрых тестов. Как видите, этапы конвейера развёртывания определяются не типами тестов, а их скоростью и областью действия. Поэтому очень разумно может быть разместить некоторые из самых узких и быстрых интеграционных тестов на ту же стадию, что и юнит-тесты — просто потому что они дают более быструю обратную связь. И необязательно проводить строгую линию по формальному типу тестов.

Избегайте дублирования тестов

Есть ещё одна ловушка, которую следует избегать: дублирование тестов на разных уровнях пирамиды. Чутьё говорит, что тестов много не бывает, но позвольте вас заверить: бывает. Каждый тест в тестовом наборе — дополнительный багаж, который не обходится бесплатно. Написание и ведение тестов требует времени. Чтение и понимание чужого теста требует времени. И конечно, выполнение тестов тоже требует времени.

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

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

Первое правило важно, потому что тесты более низкого уровня лучше позволяют сузить область и изолированно воспроизвести ошибку. Они работают быстрее и менее раздуты, что помогает при отладке вручную. И в будущем послужат хорошим регрессионным тестом. Второе правило важно для быстрого выполнения набора тестов. Если Вы уверенно протестировали все условия на тестах более низкого уровня, то в тесте более высокого уровня нет необходимости. Он просто не добавляет уверенности, что всё работает. Избыточные тесты станут обузой и начнут раздражать в повседневной работе. Набор тестов будет работать медленнее, и при изменении кода потребуется изменить больше тестов.

Сформулируем иначе: если тест более высокого уровня даёт больше уверенности, что приложение работает правильно, то нужно иметь такой тест. Написание модульного теста для класса контроллера помогает проверить логику внутри самого контроллера. Тем не менее, это не скажет вам, действительно ли конечная точка REST, которую предоставляет этот контроллер, отвечает на запросы HTTP. Таким образом, Вы продвигаетесь вверх по пирамиде тестов и добавляете тест, который проверяет именно это, но не более того. В тесте более высокого уровня Вы не тестируете всю условную логику и пограничные случаи, которые уже покрыты юнит-тестами более низкого уровня. Убедитесь, что тест высокого уровня фокусируется только на том, что не покрыто тестами более низкого уровня.

Нужно строго относиться к исключению тестов, не имеющих ценности.

  • Удалять высокоуровневые тесты, которые уже покрыты на более низком уровне (учитывая, что они не дают дополнительной ценности).
  • Заменять тесты более высокого уровня тестами более низкого уровня, если это возможно.
  • Иногда трудно удалить лишний тест, особенно если придумать его было непросто. Но Вы рискуете создать невозвратные затраты, так что смело жмите Delete. Нет причин тратить драгоценное время на тест, который перестал приносить пользу.

Пишите чистый код для тестов

Как и в отношении обычного кода, следует позаботиться о хорошем и чистом коде тестов. Вот ещё несколько советов для создания поддерживаемого тестового кода, прежде чем начинать работу и создавать автоматизированный набор тестов:

  • Тестовый код так же важен, как и код в продакшне. Уделите ему столько же заботы и внимания. «Это всего лишь тест» — не оправдание для небрежного кода.
  • Проверяйте только одно условие в каждом тесте. Тогда тесты останутся лаконичными и понятными.
  • Правило трёх А (arrange, act, assert) или триада «дано, когда, тогда» — хорошая мнемоника, чтобы поддерживать хорошую структуру тестов.
  • Читаемость имеет значение. Не злоупотребляйте DRY (правило «не повторяйся»).

Литература

  1. The Practical Test Pyramid