Утверждения Java JUnit
Проверяйте поведение в JUnit 5 с assertEquals, assertTrue, assertThrows, assertAll и другими методами.
Утверждение — это момент, когда тест перестаёт описывать и начинает проверять: оно задаёт, что должен вернуть код, и проваливает тест, если реальность не совпадает. JUnit 5 (API Jupiter) собирает все утверждения как static-методы одного класса — org.junit.jupiter.api.Assertions. Освоив этот небольшой набор методов, вы сможете выразить почти любое ожидание: равенство, истинность, нулевость, идентичность, выброшенные исключения, таймауты и групповые проверки. В этой главе рассматриваются те методы, к которым вы будете обращаться ежедневно.
Если вы новичок в этом фреймворке, сначала прочитайте введение в JUnit, затем аннотации JUnit, чтобы понять, откуда берутся методы @Test. Утверждения располагаются внутри этих методов.
Ментальная модель: проваленное утверждение проваливает тест
Метод JUnit-теста выполняется сверху вниз. Каждое утверждение, которое выполняется, не издаёт никаких звуков; первое, которое не выполняется, бросает AssertionFailedError — JUnit перехватывает его и фиксирует как провал, и на этом метод останавливается. Таким образом, утверждения являются точками выхода теста. Принятый порядок аргументов — ожидаемое первым, фактическое вторым, и каждый метод принимает необязательное финальное сообщение, которое используется только при неудаче проверки:
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
class CalculatorTest {
@Test
void addsTwoNumbers() {
int result = 2 + 3;
assertEquals(5, result, "2 + 3 should equal 5");
}
}Методы импортируются статически (import static ... Assertions.*), чтобы тело теста читалось как обычный текст — assertEquals(...), а не Assertions.assertEquals(...).
Равенство, истинность и нулевость
Эти четыре варианта покрывают большинство реальных утверждений:
| Метод | Проходит, когда |
|---|---|
assertEquals(expected, actual) | expected.equals(actual) равно true |
assertNotEquals(unexpected, actual) | значения не равны |
assertTrue(condition) / assertFalse(condition) | boolean равен true / false |
assertNull(obj) / assertNotNull(obj) | ссылка является / не является null |
assertSame(expected, actual) | оба указывают на один и тот же объект (==) |
assertArrayEquals(expected, actual) | массивы равны поэлементно |
assertEquals("HELLO", "hello".toUpperCase());
assertTrue(List.of(1, 2, 3).contains(2), "list should contain 2");
assertNull(map.get("missing"));
assertNotSame(new String("a"), new String("a")); // distinct objects
assertArrayEquals(new int[]{1, 2, 3}, computeRange(3));assertEquals использует .equals(), поэтому два разных объекта String с одинаковыми символами проходят проверку; assertSame использует ==, поэтому они её не пройдут. Используйте assertSame только тогда, когда именно идентичность объекта является предметом проверки.
Тестирование исключений с помощью assertThrows
Тест иногда утверждает, что код должен завершиться ошибкой — что некорректные данные вызывают исключение. assertThrows принимает тип исключения и лямбду; он проходит только если выполнение лямбды бросает этот тип (или подтип), и возвращает перехваченное исключение, чтобы вы могли проверить его сообщение:
@Test
void rejectsNullName() {
IllegalArgumentException ex = assertThrows(
IllegalArgumentException.class,
() -> greet(null));
assertEquals("name must not be null", ex.getMessage());
}Его зеркальным отражением является assertDoesNotThrow, который проваливается, если лямбда что-либо бросает — полезно для подтверждения того, что ранее проблемный путь теперь выполняется без ошибок. (Про правила, которым должны соответствовать исключения в вашем коде, см. исключения Java.)
Группировка с помощью assertAll
По умолчанию первое проваленное утверждение завершает метод, скрывая все последующие проблемы. assertAll выполняет каждое содержащееся утверждение, даже если некоторые проваливаются, а затем сообщает обо всех неудачах вместе — идеально для проверки нескольких свойств одного объекта:
@Test
void buildsCompleteUser() {
User u = User.of("[email protected]");
assertAll("user",
() -> assertEquals("[email protected]", u.email()),
() -> assertTrue(u.isActive()),
() -> assertNotNull(u.createdAt()));
}Если и email, и флаг активности неправильны, один запуск сообщит вам об обоих — вместо того чтобы исправлять одно, перезапускать и обнаруживать следующее.
Рабочий пример: самопроверяющийся тестовый прогон без фреймворка
В среде выполнения кода нет JUnit в classpath, поэтому эта программа реализует ту же идею на обычном Java: небольшие вспомогательные методы assertEquals, assertTrue, assertThrows и assertAll, которые ведут себя как в Jupiter — молчат при успехе, громко сообщают при неудаче — и проверяют несколько методов под тестом, выводя в конце итог в стиле test runner. API, который вы реально бы написали, показан в статических блоках выше; здесь демонстрируется то, что эти методы делают.
Что следует вынести из этого прогона:
- Прошедшие проверки не производят никаких строк с неудачами — печатаются только
add(2,3) verified == 5иtag conditions verified, отражая правило JUnit: выполненное утверждение молчит, и только неудачи говорят. assertThrowsвывелcaught expected IllegalArgumentException: name must not be null, показывая шаблон: лямбда должна бросить исключение, тип должен совпасть, а сообщение перехваченного исключения доступно для следующей проверки.assertAll ran 3 checks, 0 failedподтверждает поведение группировки — все три лямбды выполнились и были подсчитаны вместе, что именно поэтомуassertAllвыявляет все проблемы за один проход, а не останавливается на первой.- Финальный
SUMMARY -> passed: 7, failed: 0подсчитывает семь отдельных утверждений во всей программе (одно на равенство, два булевых, одно на исключение и три внутриassertAll) — такой же подсчёт ведёт реальный test runner: каждый вызовassertX— это одна проверка. - Здесь не импортировался тестовый фреймворк, но структура идентична Jupiter: вспомогательные методы, которые молчат при успехе и явно сообщают при неудаче. Замена их на
org.junit.jupiter.api.Assertions.*изменит только импорты, а не логику рассуждений о каждой проверке.
Порядок аргументов поначалу путает почти всех: это assertEquals(expected, actual), а не наоборот. Если их поменять местами, тест всё равно работает, но при неудаче сообщение читается в обратном порядке — оно утверждает, что ваше правильное значение неверно, а ошибочное — верно. Всегда ставьте литеральное или заведомо правильное значение первым.
Что дальше
Утверждения — лишь один элемент тестового метода. Чтобы использовать их эффективно:
- Организуйте настройку и завершение вокруг них с помощью колбэков жизненного цикла JUnit (
@BeforeEach,@AfterEach). - Запускайте одни и те же утверждения с множеством входных данных без копирования кода, используя параметризованные тесты.
- Вернитесь к справочнику аннотаций для
@Test,@DisplayNameи@Disabled.