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
难度
中级
JDK 支持
使用 JSpecify null 安全的 JUnit 6
可用
自 JUnit 6.0 起可用(2025 年 10 月,需要 Java 17+)
工作原理
JUnit 5 发布时没有标准化的可空性注解,让工具和开发者不得不猜测 null 行为。JUnit 6 使用 JSpecify 的 @NullMarked 将非 null 作为默认值。
相关文档