Одинокий тест (Lonely Test)

Тест проходит при запуске всего сьюта, но падает при запуске в одиночку. Причина — скрытая зависимость от состояния, которое создаётся другим тестом, @BeforeAll другого класса или статической инициализацией.

Симптомы

  • Тест падает с NullPointerException или IllegalStateException при запуске только его метода
  • В классе есть static поле, которое инициализируется в одном тест-методе и используется в другом
  • Комментарий // запускать только вместе с XxxTest в тест-классе
  • CI падает при запуске отдельных тестов (например, --tests SpecificTest), но зелёный при полном прогоне
  • Порядок выполнения тестов влияет на результат без явного @Order

Причины возникновения

  • Статическое поле инициализируется в одном тест-методе и переиспользуется в следующем
  • @BeforeAll одного класса настраивает состояние, на которое молчаливо рассчитывает другой класс
  • Тест писался только в контексте полного прогона и никогда не запускался изолированно
  • Незнание, что JUnit не гарантирует порядок тест-классов при параллельном или выборочном запуске

Почему это проблема

  • Нельзя перезапустить только упавший тест: нужно гнать весь сьют — долго и дорого
  • Скрытые зависимости между классами: тесты выглядят независимыми, но связаны через static
  • Ненадёжность на CI: CI часто запускает только упавшие тесты или тесты изменённого модуля
  • Трудная диагностика: тест падает без явной причины при изолированном запуске

Решение

Каждый тест инициализирует всё необходимое самостоятельно:

// ❌ тест 2 зависит от теста 1, упадёт при одиночном запуске
static AppConfig config;

@Test @Order(1)
void loadConfig()  { config = ConfigLoader.load("test.properties"); }

@Test @Order(2)
void dbUrlIsSet()  { assertThat(config.getDbUrl()).startsWith("jdbc:"); } // NPE без теста 1

// ✅ каждый тест самодостаточен
@Test
void dbUrlIsSet() {
    AppConfig config = ConfigLoader.load("test.properties");
    assertThat(config.getDbUrl()).startsWith("jdbc:");
}