1. 引言

本文将深入探讨 JUnit 中可用的断言机制。在之前的 从 JUnit 4 迁移到 JUnit 5JUnit 5 指南 文章基础上,我们将重点分析 JUnit 4 和 JUnit 5 中各种断言的差异与改进。

断言是测试中用于验证条件的实用方法。在 JUnit 4 中通过 Assert 类访问,而在 JUnit 5 中则通过 Assertions 类提供。为提高测试可读性,建议静态导入这些类,这样就能直接调用断言方法而无需类名前缀。

接下来我们先探索 JUnit 4 的断言功能。

2. 断言基础

断言的核心作用是验证测试中的预期条件是否成立。使用时需注意:

推荐做法:静态导入断言类

import static org.junit.Assert.*; // JUnit 4
import static org.junit.jupiter.api.Assertions.*; // JUnit 5

避免做法:每次都写完整类名

Assert.assertEquals(...); // 冗余写法
Assertions.assertEquals(...); // 冗余写法

3. JUnit 4 断言详解

JUnit 4 为所有基本类型、对象和数组(基本类型数组或对象数组)提供了断言方法。参数顺序遵循预期值在前,实际值在后的规则,可选的第一个参数是失败时显示的提示信息。

3.1. assertEquals

验证预期值与实际值是否相等:

@Test
public void whenAssertingEquality_thenEqual() {
    String expected = "Baeldung";
    String actual = "Baeldung";

    assertEquals(expected, actual);
}

可添加失败提示信息:

assertEquals("failure - strings are not equal", expected, actual);

3.2. assertArrayEquals

验证两个数组是否相等:

@Test
public void whenAssertingArraysEquality_thenEqual() {
    char[] expected = {'J','u','n','i','t'};
    char[] actual = "Junit".toCharArray();
    
    assertArrayEquals(expected, actual);
}

⚠️ 特殊处理:两个 null 数组会被视为相等:

@Test
public void givenNullArrays_whenAssertingArraysEquality_thenEqual() {
    int[] expected = null;
    int[] actual = null;

    assertArrayEquals(expected, actual);
}

3.3. assertNotNull 和 assertNull

验证对象是否为 null:

@Test
public void whenAssertingNull_thenTrue() {
    Object car = null;
    
    assertNull("The car should be null", car);
}

assertNotNull 则验证对象非 null。

3.4. assertNotSame 和 assertSame

验证两个变量是否引用同一对象:

@Test
public void whenAssertingNotSameObject_thenDifferent() {
    Object cat = new Object();
    Object dog = new Object();

    assertNotSame(cat, dog);
}

assertSame 用于验证两者引用相同对象。

3.5. assertTrue 和 assertFalse

验证布尔条件是否成立:

@Test
public void whenAssertingConditions_thenVerified() {
    assertTrue("5 is greater then 4", 5 > 4);
    assertFalse("5 is not greater then 6", 5 > 6);
}

3.6. fail

直接使测试失败,常用于验证异常或标记未完成测试:

@Test
public void whenCheckingExceptionMessage_thenEqual() {
    try {
        methodThatShouldThrowException();
        fail("Exception not thrown");
    } catch (UnsupportedOperationException e) {
        assertEquals("Operation Not Supported", e.getMessage());
    }
}

3.7. assertThat

注意参数顺序与其他断言不同:实际值在前,匹配器在后:

@Test
public void testAssertThatHasItems() {
    assertThat(
      Arrays.asList("Java", "Kotlin", "Scala"), 
      hasItems("Java", "Kotlin"));
}

💡 更强大的用法参考 Hamcrest 测试指南

4. JUnit 5 断言增强

JUnit 5 保留了 JUnit 4 的大部分断言,同时利用 Java 8 特性新增了多个实用断言。主要改进包括:

  • 参数顺序调整:失败提示信息移至最后
  • 支持 Lambda:消息可使用 Supplier 实现懒加载
  • 新增断言:如 assertAll, assertThrows

4.1. assertArrayEquals

验证数组相等性,支持懒加载消息:

@Test
public void whenAssertingArraysEquality_thenEqual() {
    char[] expected = { 'J', 'u', 'p', 'i', 't', 'e', 'r' };
    char[] actual = "Jupiter".toCharArray();

    assertArrayEquals(expected, actual, "Arrays should be equal");
}

4.2. assertEquals

浮点数比较支持误差范围(delta):

@Test
void whenAssertingEqualityWithDelta_thenEqual() {
    float square = 2 * 2;
    float rectangle = 3 * 2;
    float delta = 2;

    assertEquals(square, rectangle, delta);
}

4.3. assertTrue 和 assertFalse

支持 BooleanSupplier 替代布尔条件:

@Test
public void givenBooleanSupplier_whenAssertingCondition_thenVerified() {
    BooleanSupplier condition = () -> 5 > 6;

    assertFalse(condition, "5 is not greater then 6");
}

4.4. assertNull 和 assertNotNull

懒加载消息示例:

@Test
void whenAssertingNotNull_thenTrue() {
    Object dog = new Object();

    assertNotNull(dog, () -> "The dog should not be null");
}

4.5. assertSame 和 assertNotSame

对象引用验证:

@Test
void whenAssertingSameObject_thenSuccessfull() {
    String language = "Java";
    Optional<String> optional = Optional.of(language);

    assertSame(language, optional.get());
}

4.6. fail

支持带原因的失败标记:

@Test
public void whenFailingATest_thenFailed() {
    // Test not completed
    fail("FAIL - test not completed");
}

4.7. assertAll

分组断言:执行所有断言后统一报告失败:

@Test
void givenMultipleAssertion_whenAssertingAll_thenOK() {
    Object obj = null;
    assertAll(
      "heading",
      () -> assertEquals(4, 2 * 2, "4 is 2 times 2"),
      () -> assertEquals("java", "JAVA".toLowerCase()),
      () -> assertNull(obj, "obj is null")
    );
}

⚠️ 仅当遇到 OutOfMemoryError 等严重异常时才会中断执行。

4.8. assertIterableEquals

深度验证可迭代对象(类型可不同):

@Test
void givenTwoLists_whenAssertingIterables_thenEquals() {
    Iterable<String> al = new ArrayList<>(asList("Java", "Junit", "Test"));
    Iterable<String> ll = new LinkedList<>(asList("Java", "Junit", "Test"));

    assertIterableEquals(al, ll);
}

4.9. assertLinesMatch

字符串列表匹配算法:

  1. 精确匹配
  2. 正则匹配
  3. 快进标记处理
@Test
void whenAssertingEqualityListOfStrings_thenEqual() {
    List<String> expected = asList("Java", "\\d+", "JUnit");
    List<String> actual = asList("Java", "11", "JUnit");

    assertLinesMatch(expected, actual);
}

4.10. assertNotEquals

验证不相等(两个 null 会失败):

@Test
void whenAssertingEquality_thenNotEqual() {
    Integer value = 5; // result of an algorithm
    
    assertNotEquals(0, value, "The result cannot be 0");
}

4.11. assertThrows

异常验证的优雅方案

@Test
void whenAssertingException_thenThrown() {
    Throwable exception = assertThrows(
      IllegalArgumentException.class, 
      () -> {
          throw new IllegalArgumentException("Exception message");
      }
    );
    assertEquals("Exception message", exception.getMessage());
}

4.12. assertTimeout 和 assertTimeoutPreemptively

超时验证对比:

方法 执行方式 超时处理
assertTimeout 同线程执行 不中断任务
assertTimeoutPreemptively 独立线程执行 强制中断
@Test
void whenAssertingTimeout_thenNotExceeded() {
    assertTimeout(
      ofSeconds(2), 
      () -> {
        // code that requires less than 2 minutes to execute
        Thread.sleep(1000);
      }
    );
}

5. 总结

本文全面对比了 JUnit 4 和 JUnit 5 的断言机制,重点突出了 JUnit 5 的改进:

  • 参数顺序优化:失败消息后置更符合直觉
  • Lambda 支持:实现懒加载和更灵活的条件判断
  • 新增断言assertAll 分组验证、assertThrows 异常验证等
  • 超时控制增强assertTimeoutPreemptively 提供强制中断能力

完整示例代码请访问 GitHub 项目


原始标题:Assertions in JUnit 4 and JUnit 5