JUnit依靠Java反射机制在运行期动态发现并调用被@Test注解标记的方法:先获取Class对象,再通过getDeclaredMethods()扫描所有方法,检查@Test注解(需@Retention(RUNTIME)),最后invoke执行;同时按注解类型分组处理@Before/@After等生命周期方法,确保测试隔离性与灵活性。

JUnit 能自动运行带 @Test 的方法,靠的不是魔法,而是 Java 反射机制在运行期动态发现并调用这些方法。
反射是 JUnit 找到测试方法的关键
JUnit 启动时,并不知道你写了几个 @Test 方法。它先通过反射获取测试类的 Class 对象,再调用 getDeclaredMethods() 获取所有声明的方法,逐个检查是否被 @Test 注解标记。
- 注解必须声明为
@Retention(RetentionPolicy.RUNTIME),否则运行期看不到 - 反射不关心方法名是否以 test 开头——那是旧习惯;真正起作用的是 @Test 注解本身
- 即使方法是 private,只要加了 @Test 且注解保留策略正确,反射也能拿到并设为可访问(
method.setAccessible(true))
JUnit 如何依次执行测试生命周期
除了 @Test,JUnit 还要识别 @Before、@After、@BeforeClass 等注解。它用反射做三件事:
- 扫描所有方法,按注解类型分组:比如把所有 @Before 方法放进一个列表
- 对 @BeforeClass 和 @AfterClass,只找 static 方法;对 @Before/@After,只找实例方法
- 每次运行一个 @Test 方法前,先用反射调用对应的 @Before 实例方法;结束后再调用 @After
为什么需要反射?直接写死不行吗
硬编码方法名会彻底破坏灵活性:
- 无法支持任意命名的测试方法(比如
testUserLoginWithInvalidToken) - 无法在运行时根据配置跳过某些测试(@Ignore 或条件化启用)
- 框架无法通用:每个新测试类都要改 JUnit 源码,违背“开闭原则”
- 像 Mockito、Spring Test 这类扩展,也依赖反射注入 mock 对象或上下文,没有反射就无法集成
一个极简模拟:用反射跑 @Demo 方法
你可以自己写几行代码验证这个过程:
- 定义注解
@interface Demo { },加上@Retention(RUNTIME) - 在某个类里写几个带
@Demo的 public void 方法 - 用
Class.forName("xxx.TestClass").getDeclaredMethods()遍历 - 对每个 method 调用
method.isAnnotationPresent(Demo.class) - 满足条件就用
method.invoke(instance)执行
这正是 JUnit 最核心的动作——只是它封装得更健壮,还加了异常捕获、计时、结果统计等能力。

















