Java 单元测试的框架选型其实没那么复杂:JUnit 5 是单测事实标准,TestNG 强在套件编排和并行,Mockito 负责把外部依赖挡在测试之外。大多数团队真正踩的坑不在于「用哪个」,而在于把环境初始化塞进每个方法、用 mock 替掉自己不拥有的类型、或者用测试去验证调用次数而不是行为。文末附速查表。


一、JUnit:Java 单测事实标准

JUnit 5 拆成 Platform / Jupiter / Vintage 三层:常用注解有 @Test、@ParameterizedTest、@ExtendWith。断言用 Assertions,生命周期用 @BeforeEach。嵌套测试用 @Nested 表达场景。JUnit 4 的 @RunWith 在迁移时用 Vintage 兼容。

几个容易踩的点

  • 测实现细节。好测试要快、隔离、名字表达行为。不要测实现细节。
  • 复制粘贴用例。参数化优于复制粘贴,@ParameterizedTest 比手写多份用例更稳。
  • 把覆盖率当目标。覆盖率是辅助不是目标,别为了数字堆无意义的断言。
  • Android 用错运行器。Android 上 JVM 单测用 Robolectric 或纯 Java 模块;仪器测走 AndroidJUnitRunner。

什么时候用:纯单模块单元测试默认 JUnit 5。CI 里失败即停还是聚合报告,看团队习惯。


二、TestNG:套件、依赖、分组与并行

TestNG 比 JUnit 4 更强调套件、依赖、分组和并行。它用 @Test、@BeforeMethod、@BeforeSuite 等注解描述测试方法,用 testng.xml 跨 class 编排 suite,数据驱动用 DataProvider,失败重试用 retry analyzer。软断言 SoftAssert 适合一批校验一起失败。和 Spring 集成用 AbstractTestNGSpringContextTests,Maven surefire 要指定 suiteXmlFile。很多接口自动化和测试平台选它,是因为一套 XML 就能把冒烟、回归和分组跑起来;和 JUnit 5 相比,依赖测试和灵活套件仍是它的长处。

三个容易踩的坑

  • 环境初始化放进每个方法。生命周期按 suite、test、class、method 分层:BeforeSuite 放昂贵的环境准备,不要在每个方法里连一次数据库。
  • 依赖链脆弱。dependsOnMethods 能串流程,但失败会跳过后续,稳定性不如显式编排。
  • 并行串数据。thread-count 开起来后静态变量和共享 driver 会串数据,每个线程用自己的上下文;并行时注意线程安全的测试夹具。

什么时候用:接口自动化、多分组回归和需要 XML 编排的测试仓库适合 TestNG。纯单模块单测用 JUnit 5 更轻。接入时先写通一条带 DataProvider 的用例,再上并行和监听器报结果。


三、Mockito:用动态代理 / 字节码造假对象

用动态代理 / 字节码造假对象:mock、when / thenReturn、verify。spy 包装真实对象。参数匹配器 any() 要全用匹配器。静态方法在 mockito-inline 里用 mockStatic。ArgumentCaptor 拿回调参数。

几个容易踩的坑

  • mock 自己不拥有的类型。不要 mock 你不拥有的类型(如 Android 框架),优先伪造自己的接口;能写假实现就别 mock。
  • verifyNoMoreInteractions 太脆。它容易脆,别拿它当硬约束。
  • Kotlin / Android 用错工具。Kotlin 项目可用 MockK;Android 仪器测里 Mockito 常需 dexmaker。

什么时候用:测试应验证行为而非调用次数。Kotlin 项目用 MockK 比硬套 Mockito 的 JVM 写法顺手;Android 仪器测记得配 dexmaker。


四、选型与用法边界:JUnit 5 还是 TestNG

单测用 JUnit 5,需要 XML 套件 / 依赖顺序 / 并行接口自动化用 TestNG,挡外部依赖用 Mockito。两者并非互斥——很多项目单测用 JUnit 5,集成测试仓库用 TestNG,Mockito 在两边都用。

  • 已有 TestNG 生态或需要依赖测试顺序:留 TestNG。
  • 新项目、纯单模块单测:更常见选 JUnit 5。
  • mock 的底线:只 mock 自己拥有的接口,不拥有的类型去写 Fake;验证行为而不是调用次数。

附:Java 单元测试框架速查表

场景 用什么 不要用什么
纯单模块单元测试 JUnit 5(@ParameterizedTest、@Nested) 测实现细节、为覆盖率堆无意义断言
接口自动化 / 多分组回归 / XML 编排 TestNG(DataProvider、testng.xml) 把环境初始化放进每个 @Test、依赖链串太深
挡外部依赖 Mockito(mock / spy / ArgumentCaptor) mock 不拥有的类型、verifyNoMoreInteractions 当硬约束
Kotlin 项目 MockK 硬套 Mockito 的 JVM 写法
Android 仪器测 Robolectric / AndroidJUnitRunner + dexmaker 用错 JUnit 运行器、Mockito 不配 dexmaker