Хороший тест:
- проверяет одно. примечание: это не равносильно «имеет только одно утверждение». «вещью» может быть «пользователь обновлен», что может потребовать нескольких утверждений для отдельных полей.
- это «одно» четко указано в названии теста
- не выполняет никакой другой дублирующей проверки (например, если есть тест, проверяющий, что носитель может быть вставлен, то никакие другие тесты не должны иметь такую же проверку после того, как они вставляют носитель для целей тестирования)
- создает любые данные, необходимые для работы. Никакой зависимости от каких-то магических данных, ранее созданных кем-то вручную.
- при выходе — удаляет все созданные им данные (в блоке «finally»).
- не мешает другим тестам, которые выполняются параллельно в той же JVM
- не мешает параллельной работе другой копии ТОГО ЖЕ теста (в другой JVM) — имейте это в виду при создании интеграционных тестов: много копий одного и того же теста могут выполняться одновременно в разных JVM. вы не хотите, чтобы один экземпляр создавал данные в базе данных, а другой экземпляр удалял эти данные одновременно. для этого обычно требуется генерировать некоторые уникальные идентификаторы данных или что-то в этом роде.
- использует реалистичную настройку (остерегайтесь опасности издеваться над слишком большой частью вашей системы — в конечном итоге вы тестируете свои макеты, а не реальный код). Я видел это не раз…
- имеет длину менее ~15 строк (ради удобочитаемости, плюс это просто естественный побочный эффект требований, перечисленных выше).
- не зависит от какого-либо конкретного порядка выполнения тестов. см. примеры ниже, как разделить длинный тест «счастливый путь» на несколько тестовых методов, каждый из которых проверяет одну вещь.
- является детерминированным: дает один и тот же результат 10 раз из 10. если он работает 9 раз из 10, удалите его и напишите другой, детерминированный. с увеличением количества тестов в вашем проекте вы можете сделать простые расчеты и посмотреть, как скоро ваш набор тестов перестанет быть полезным, если каждый отдельный тест дает правильный результат только в 9 из 10 раз.
примечание об интеграционных тестах: это предполагает, что внешняя система остается «такой же». написание интеграционных тестов для внешней системы всегда будет набором компромиссов, но его можно облегчить, используя объекты-заглушки — вот где в игру вступает «программирование для интерфейсов». - Бонусные баллы: используются утверждения в стиле fest (см. синтаксис «assertThat» из библиотеки AssertJ), а не старый стиль JUnit «assertEquals».
- соответствует тому же уровню стандартов качества кода, что и производственный код.
Речь шла об индивидуальных испытаниях.
Теперь по тестам в целом:
- Им нужно проверить многие важные пути, а не только «счастливый путь».
- Необходимо использовать реалистичную настройку. Например. если у вас есть веб-приложение с конечными точками REST, развернутыми на Tomcat, то запуск встроенных тестов в Jetty будет проверкой того, что ваша настройка Jetty, созданная на основе тестов, работает. Что имеет мало общего с вашей реальной производственной установкой, которую вам ДЕЙСТВИТЕЛЬНО нужно протестировать.
Теперь несколько примеров того, что я считаю «хорошими» и «плохими» тестами.
Хороший(*) тест (версия Java):
* за исключением жестко заданного идентификатора местоположения, который я бы предложил заменить генерированием какого-либо случайного идентификатора.
@Test public void locationIsInserted() throws Exception { String locationId = "123"; String ownerId = "234"; try { LocationVerifier.verifyLocationDoNotExist(locationId); Location location = LocationGenerator.generateForLocationId(locationId); DB.insertLocation(ownerId, location); Location found = DB.findLocation(locationId); assertThat(found.getFlag()) .isEqualTo(location.getFlag()); } finally { Cleaner.deleteLocation(locationId); } }
Плохой тест:
Выложил пример кода. Посмотрел. Плакала несколько минут. Удалил.