2.6 TestExecutionListener

TestExecutionListener` 是 Spring Test 的核心扩展接口,允许开发者监听测试执行的生命周期事件,并插入自定义逻辑。通过实现此接口,可以干预测试的 准备阶段执行阶段清理阶段,适用于监控、资源管理、日志记录等场景。

1. TestExecutionListener 核心方法

接口定义了以下关键方法(均为默认方法,可按需重写):

方法名 触发时机 典型用途
beforeTestClass 测试类开始执行前 初始化全局资源(如启动 Docker 容器)
prepareTestInstance 测试实例创建后,依赖注入前 修改测试实例属性
beforeTestMethod 每个 @Test 方法执行前 准备测试数据
beforeTestExecution @Test 方法执行前(在 @BeforeEach 之后) 最后时刻的预处理
afterTestExecution @Test 方法执行后(在 @AfterEach 之前) 收集测试结果或清理临时资源
afterTestMethod 每个 @Test 方法执行后 清理测试数据
afterTestClass 测试类所有方法执行完毕 释放全局资源(如关闭连接)

2. 实现自定义监听器

场景 1:统计测试方法执行时间

目标:在控制台输出每个测试方法的执行耗时。

public class TimingExecutionListener implements TestExecutionListener {  
    private Map<String, Long> startTimes = new HashMap<>();  

    @Override  
    public void beforeTestExecution(TestContext testContext) {  
        String testName = testContext.getTestMethod().getName();  
        startTimes.put(testName, System.currentTimeMillis());  
    }  

    @Override  
    public void afterTestExecution(TestContext testContext) {  
        String testName = testContext.getTestMethod().getName();  
        long duration = System.currentTimeMillis() - startTimes.get(testName);  
        System.out.printf("测试方法 [%s] 执行耗时: %d ms%n", testName, duration);  
    }  
}  

场景 2:自动清理临时文件

目标:在每个测试方法结束后删除临时文件。

public class TempFileCleanupListener implements TestExecutionListener {  
    @Override  
    public void afterTestExecution(TestContext testContext) {  
        Path tempDir = Paths.get("temp");  
        if (Files.exists(tempDir)) {  
            try {  
                Files.walk(tempDir)  
                     .sorted(Comparator.reverseOrder())  
                     .forEach(path -> {  
                         try {  
                             Files.delete(path);  
                         } catch (IOException e) {  
                             e.printStackTrace();  
                         }  
                     });  
                System.out.println("已清理临时目录: " + tempDir);  
            } catch (IOException e) {  
                throw new RuntimeException("清理临时文件失败", e);  
            }  
        }  
    }  
}  

3. 注册自定义监听器

通过 @TestExecutionListeners 注解将监听器绑定到测试类。

示例:注册多个监听器

@ExtendWith(SpringExtension.class)  
@ContextConfiguration(classes = AppConfig.class)  
@TestExecutionListeners(  
    listeners = {  
        TimingExecutionListener.class,  
        TempFileCleanupListener.class  
    },  
    mergeMode = MergeMode.MERGE_WITH_DEFAULTS // 保留默认监听器(如依赖注入、事务管理)  
)  
public class CustomListenerTest {  
    @Test  
    void testMethod1() throws InterruptedException {  
        Thread.sleep(500);  
    }  

    @Test  
    void testMethod2() throws InterruptedException {  
        // 创建临时文件(示例)  
        Path tempFile = Paths.get("temp/test.txt");  
        Files.createDirectories(tempFile.getParent());  
        Files.write(tempFile, "Hello".getBytes());  
        Thread.sleep(300);  
    }  
}  

控制台输出

测试方法 [testMethod1] 执行耗时: 502 ms  
已清理临时目录: temp  
测试方法 [testMethod2] 执行耗时: 301 ms  

4. 默认监听器与执行顺序

Spring Test 默认注册了多个核心监听器,按以下顺序执行:

  1. DependencyInjectionTestExecutionListener:处理依赖注入。
  2. DirtiesContextTestExecutionListener:处理 @DirtiesContext 注解。
  3. TransactionalTestExecutionListener:管理事务。

自定义监听器的执行顺序

  • 通过 @TestExecutionListenerslisteners 数组顺序控制。
  • 默认在默认监听器 之后 执行(若 mergeMode = MERGE_WITH_DEFAULTS)。

5. 高级场景:动态模拟外部服务

需求

在测试方法执行前,自动模拟外部服务(如 HTTP API),并在测试后重置。

实现监听器

public class MockExternalServiceListener implements TestExecutionListener {  
    private MockWebServer mockServer;  

    @Override  
    public void beforeTestClass(TestContext testContext) {  
        // 启动模拟服务器  
        mockServer = new MockWebServer();  
        mockServer.start();  
        // 将模拟 URL 注入到 Spring 环境  
        String mockUrl = mockServer.url("/").toString();  
        testContext.getApplicationContext()  
                  .getEnvironment()  
                  .getPropertySources()  
                  .addFirst(new MapPropertySource("mock", Map.of("external.service.url", mockUrl)));  
    }  

    @Override  
    public void beforeTestMethod(TestContext testContext) {  
        // 根据测试方法配置模拟响应  
        String methodName = testContext.getTestMethod().getName();  
        if ("testExternalService".equals(methodName)) {  
            mockServer.enqueue(new MockResponse().setBody("Mock Response"));  
        }  
    }  

    @Override  
    public void afterTestClass(TestContext testContext) {  
        // 关闭模拟服务器  
        try {  
            mockServer.shutdown();  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
    }  
}  

使用示例

@ExtendWith(SpringExtension.class)  
@ContextConfiguration(classes = AppConfig.class)  
@TestExecutionListeners(  
    listeners = MockExternalServiceListener.class,  
    mergeMode = MergeMode.MERGE_WITH_DEFAULTS  
)  
public class ExternalServiceTest {  
    @Autowired  
    private ExternalServiceClient client;  

    @Test  
    void testExternalService() {  
        String response = client.callExternalService();  
        assertEquals("Mock Response", response);  
    }  
}  

6. 注意事项

  1. 避免副作用:确保监听器逻辑不会意外修改测试状态。
  2. 性能影响:避免在监听器中执行耗时操作(如频繁 I/O)。
  3. 执行顺序依赖:若多个监听器存在依赖关系,需通过 order 属性或数组顺序控制。

通过 TestExecutionListener,开发者可以深度定制 Spring 测试流程,实现诸如 性能监控环境隔离动态 Mock 等高级功能,显著提升测试的灵活性和可维护性。

你可能感兴趣的:(springtest,spring,junit)