Одинокий тест (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:");
}