Хороший тест:

  • проверяет одно. примечание: это не равносильно «имеет только одно утверждение». «вещью» может быть «пользователь обновлен», что может потребовать нескольких утверждений для отдельных полей.
  • это «одно» четко указано в названии теста
  • не выполняет никакой другой дублирующей проверки (например, если есть тест, проверяющий, что носитель может быть вставлен, то никакие другие тесты не должны иметь такую ​​​​же проверку после того, как они вставляют носитель для целей тестирования)
  • создает любые данные, необходимые для работы. Никакой зависимости от каких-то магических данных, ранее созданных кем-то вручную.
  • при выходе — удаляет все созданные им данные (в блоке «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);
    }
}

Плохой тест:

Выложил пример кода. Посмотрел. Плакала несколько минут. Удалил.