JUnit4 测试生命周期详解
概述
理解JUnit4测试生命周期是编写有效单元测试的关键。本文件详细说明测试的执行流程、实例化策略、注解执行顺序等核心概念。
1. 测试生命周期总览
完整的测试执行流程图
┌─────────────────────────────────────────────────────┐
│ 测试类加载 │
├─────────────────────────────────────────────────────┤
│ @BeforeClass 方法执行 │
│ (整个类生命周期只执行一次) │
├─────────────────────────────────────────────────────┤
│ │
│ 对于每个@Test方法: │
│ ┌─────────────────────────────────────────────┐ │
│ │创建新的测试实例│ │
│ ├─────────────────────────────────────────────┤ │
│ │ @Before 方法执行 │ │
│ ├─────────────────────────────────────────────┤ │
│ │ @Test 方法执行 │ │
│ ├─────────────────────────────────────────────┤ │
│ │ 可能执行:@Test(expected) │ │
│ │ 可能执行:@Test(timeout) │ │
│ ├─────────────────────────────────────────────┤ │
│ │ @After 方法执行 │ │
│ │ (即使@Test抛出异常也会执行) │ │
│ └─────────────────────────────────────────────┘ │
│ │
├─────────────────────────────────────────────────────┤
│ @AfterClass 方法执行 │
│ (整个类生命周期只执行一次) │
└─────────────────────────────────────────────────────┘
2. 测试类实例化策略
2.1 默认行为:每个测试方法一个实例
JUnit4默认情况下为每个@Test方法创建新的测试实例
importorg.junit.Before;importorg.junit.Test;importstaticorg.junit.Assert.*;publicclassTestInstancePerMethodTest{privateintinstanceCounter=0;publicTestInstancePerMethodTest(){System.out.println("构造函数被调用,创建新实例");instanceCounter=0;// 每次创建新实例时重置}@BeforepublicvoidsetUp(){instanceCounter++;System.out.println("setUp() 执行,instanceCounter: "+instanceCounter);}@TestpublicvoidtestMethod1(){System.out.println("testMethod1 执行,instanceCounter: "+instanceCounter);// 这个实例的instanceCounter是1assertEquals(1,instanceCounter);}@TestpublicvoidtestMethod2(){System.out.println("testMethod2 执行,instanceCounter: "+instanceCounter);// 新的实例,所以instanceCounter也是1assertEquals(1,instanceCounter);}}/* 输出示例: 构造函数被调用,创建新实例 setUp() 执行,instanceCounter: 1 testMethod1 执行,instanceCounter: 1 构造函数被调用,创建新实例 setUp() 执行,instanceCounter: 1 testMethod2 执行,instanceCounter: 1 */2.2 验证不同实例
publicclassDifferentInstancesTest{privateintinstanceId=(int)(Math.random()*1000);@TestpublicvoidtestInstance1(){System.out.println("testInstance1 - instanceId: "+instanceId);// 保存当前实例IDintcurrentId=instanceId;// 修改实例变量instanceId=999;// 验证修改只影响当前实例assertEquals(999,instanceId);// 在其他测试中这个修改不会生效}@TestpublicvoidtestInstance2(){System.out.println("testInstance2 - instanceId: "+instanceId);// 这是一个新的实例,所以instanceId是新的随机值// 不是999!assertNotEquals(999,instanceId);}}2.3 为什么每个测试方法创建新实例?
设计目的:
测试隔离:确保测试之间不会相互影响
线程安全:每个测试在独立的实例上运行
状态重置:避免测试间的状态污染
3. 注解执行顺序详解
3.1 完整的执行顺序
BeforeClass>{构造函数>Before>Test>After} * N>AfterClass
importorg.junit.*;publicclassFullLifecycleTest{// ===== 类级别生命周期 =====@BeforeClasspublicstaticvoidbeforeClass(){System.out.println("1. @BeforeClass - 类级别初始化");}@AfterClasspublicstaticvoidafterClass(){System.out.println("9. @AfterClass - 类级别清理");}// ===== 实例级别生命周期 =====@Beforepublicvoidbefore(){System.out.println(" 3. @Before - 测试方法前执行");}@Afterpublicvoidafter(){System.out.println(" 5. @After - 测试方法后执行");}// ===== 测试方法 =====@TestpublicvoidtestOne(){System.out.println(" 4. @Test testOne 执行");}@TestpublicvoidtestTwo(){System.out.println(" 4. @Test testTwo 执行");}// ===== 构造函数 =====publicFullLifecycleTest(){System.out.println("2. 构造函数 - 创建测试实例");}}/** * 1. @BeforeClass - 类级别初始化 * 2. 构造函数 - 创建测试实例 * 3. @Before - 测试方法前执行 * 4. @Test testOne 执行 * 5. @After - 测试方法后执行 * 2. 构造函数 - 创建测试实例 * 3. @Before - 测试方法前执行 * 4. @Test testTwo 执行 * 5. @After - 测试方法后执行 * 9. @AfterClass - 类级别清理 */3.2 异常情况下的生命周期
场景1:@Before抛出异常
@Before异常了,@After还是会执行的,只不过@Test不会执行了
publicclassExceptionInBeforeTest{@BeforepublicvoidsetUp(){System.out.println("@Before 执行");thrownewRuntimeException("@Before 中发生异常");}@AfterpublicvoidtearDown(){System.out.println("@After 执行");}@TestpublicvoidtestMethod(){System.out.println("测试方法执行");fail("这个测试不应该执行");}}/* 输出: @Before 执行 @After 执行 结果:测试失败,@Test方法不会执行,但@After仍然执行 */场景2:@Test抛出异常
即使@Test报错了,@After还是会执行的
publicclassExceptionInTestTest{@BeforepublicvoidsetUp(){System.out.println("@Before 执行");}@AfterpublicvoidtearDown(){System.out.println("@After 执行(即使@Test抛出异常)");}@Test(expected=RuntimeException.class)publicvoidtestWithExpectedException(){System.out.println("抛出期望的异常");thrownewRuntimeException("测试异常");}@TestpublicvoidtestWithUnexpectedException(){System.out.println("抛出未期望的异常");thrownewRuntimeException("意外异常");}}/* 第一个测试输出: @Before 执行 抛出期望的异常 @After 执行(即使@Test抛出异常) 结果:测试通过(因为异常符合expected) 第二个测试输出: @Before 执行 抛出未期望的异常 @After 执行(即使@Test抛出异常) 结果:测试失败(未期望的异常) */场景3:@After抛出异常
publicclassExceptionInAfterTest{@TestpublicvoidtestSuccess(){System.out.println("测试成功执行");}@TestpublicvoidtestFailure(){System.out.println("测试失败执行");fail(<