Tooling 中级

使用 JSpecify null 安全的 JUnit 6

JUnit 6 采用 JSpecify @NullMarked,使 null 契约在整个测试 API 中显式化。

✕ JUnit 5
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

class UserServiceTest {

    // JUnit 5: no null contracts on the API
    // Can assertEquals() accept null? Check source...
    // Does fail(String) allow null message? Unknown.

    @Test
    void findUser_found() {
        // Is result nullable? API doesn't say
        User result = service.findById("u1");
        assertNotNull(result);
        assertEquals("Alice", result.name());
    }

    @Test
    void findUser_notFound() {
        // Hope this returns null, not throws...
        assertNull(service.findById("missing"));
    }
}
✓ JUnit 6
import org.junit.jupiter.api.Test;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import static org.junit.jupiter.api.Assertions.*;

@NullMarked  // all refs non-null unless @Nullable
class UserServiceTest {

    // JUnit 6 API is @NullMarked:
    // assertNull(@Nullable Object actual)
    // assertEquals(@Nullable Object, @Nullable Object)
    // fail(@Nullable String message)

    @Test
    void findUser_found() {
        // IDE warns: findById returns @Nullable User
        @Nullable User result = service.findById("u1");
        assertNotNull(result); // narrows type to non-null
        assertEquals("Alice", result.name()); // safe
    }

    @Test
    void findUser_notFound() {
        @Nullable User result = service.findById("missing");
        assertNull(result); // IDE confirms null expectation
    }
}
发现此代码有问题? 告诉我们。
📜

显式契约

@NullMarked 在 JUnit 6 模块上直接记录 null 语义。

🔒

工具支持

IDE 和分析器可以在测试代码中标记 null 违规。

🧹

更少 @Nullable

非 null 是默认值——只有异常需要注解。

旧方式
未注解的 API
现代方式
@NullMarked API
自 JDK
17
难度
中级
使用 JSpecify null 安全的 JUnit 6
可用

自 JUnit 6.0 起可用(2025 年 10 月,需要 Java 17+)

JUnit 5 发布时没有标准化的可空性注解,让工具和开发者不得不猜测 null 行为。JUnit 6 使用 JSpecify 的 @NullMarked 将非 null 作为默认值。

分享 𝕏 🦋 in