You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现可代理标准Runner的自定义JUnit Runner以兼容多框架?

方案:委托模式+自定义注解实现可复用的JUnit Runner

没问题,我帮你设计一个既可以复用MockitoJUnitRunner、SpringRunner等现有Runner的功能,又能在测试方法前后插入自定义逻辑的方案,核心思路是用委托模式+自定义注解来实现,完全不用重复造轮子!

1. 第一步:定义自定义注解@MyConfig

先搞一个注解,用来标记测试类,同时允许你指定要复用的基础Runner(比如Mockito或者Spring的Runner):

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.runner.Runner;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyConfig {
    // 默认用BlockJUnit4ClassRunner,也可以指定其他Runner
    Class<? extends Runner> value() default org.junit.runners.BlockJUnit4ClassRunner.class;
}

2. 第二步:实现自定义Runner(核心逻辑)

这个Runner不直接写测试执行逻辑,而是委托给你指定的目标Runner,同时通过JUnit的Statement接口来包装测试方法,插入你的自定义前后置代码:

import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.Statement;

public class MyRunner extends BlockJUnit4ClassRunner {
    private final Runner delegateRunner;
    private MyApi api = new MyApi();

    public MyRunner(Class<?> klass) throws InitializationError {
        super(klass);
        // 读取@MyConfig注解,拿到用户指定的Runner
        MyConfig config = klass.getAnnotation(MyConfig.class);
        if (config == null) {
            throw new InitializationError("测试类必须添加@MyConfig注解哦");
        }
        try {
            // 实例化用户指定的委托Runner
            delegateRunner = config.value().getConstructor(Class.class).newInstance(klass);
        } catch (Exception e) {
            throw new InitializationError("初始化委托Runner失败:" + e.getMessage());
        }
    }

    // 把测试名称的获取委托给目标Runner
    @Override
    public String getTestName(FrameworkMethod method) {
        return delegateRunner.getTestName(method);
    }

    // 核心:拦截测试方法执行,包装前后置逻辑
    @Override
    protected Statement methodBlock(FrameworkMethod method) {
        // 先拿到目标Runner原本的执行逻辑
        Statement originalStatement = delegateRunner instanceof BlockJUnit4ClassRunner
                ? ((BlockJUnit4ClassRunner) delegateRunner).methodBlock(method)
                : super.methodBlock(method);

        // 包装自定义的前后置操作
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                // 测试方法执行前的自定义逻辑
                api.beforeTestMethod(method.getName());
                try {
                    // 执行原有测试逻辑(包括目标Runner的功能,比如Mockito初始化、Spring上下文加载)
                    originalStatement.evaluate();
                } finally {
                    // 测试方法执行后的自定义逻辑,不管成功失败都会执行
                    api.afterTestMethod(method.getName());
                }
            }
        };
    }

    // 如果需要,也可以把类级别的执行逻辑委托给目标Runner(比如Spring的上下文初始化)
    @Override
    protected Statement classBlock() {
        if (delegateRunner instanceof BlockJUnit4ClassRunner) {
            return ((BlockJUnit4ClassRunner) delegateRunner).classBlock();
        }
        return super.classBlock();
    }
}

3. 第三步:使用示例

比如你要结合MockitoJUnitRunner,只需要在测试类上同时加@RunWith(MyRunner.class)和@MyConfig(MockitoJUnitRunner.class):

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;

@RunWith(MyRunner.class)
@MyConfig(MockitoJUnitRunner.class)
public class MockitoTest {
    @Mock
    private UserService userService;

    @Test
    public void testUserQuery() {
        // 这里可以正常使用Mockito的Mock对象,同时你的MyApi会在测试前后执行自定义逻辑
    }
}

如果要结合SpringRunner,只需要把@MyConfig的value改成SpringRunner.class就行:

@RunWith(MyRunner.class)
@MyConfig(SpringRunner.class)
@SpringBootTest
public class SpringTest {
    @Autowired
    private OrderService orderService;

    @Test
    public void testOrderCreate() {
        // 正常使用Spring的依赖注入,同时你的自定义前后置逻辑生效
    }
}

关键知识点说明

  • 委托模式的优势:解决了JUnit Runner单继承的限制,不用继承多个Runner就能复用它们的功能,灵活组合不同的Runner特性。
  • Statement接口的作用:JUnit用Statement来封装每一个测试执行步骤,通过包装Statement,我们可以在不修改原有Runner代码的前提下,轻松插入自定义的前后置操作。
  • 自定义注解的灵活性:通过@MyConfig,你可以给不同的测试类指定不同的基础Runner,不用修改MyRunner的代码。

注意事项

  • 确保你指定的基础Runner有一个接收Class<?>参数的构造方法(大多数官方Runner都符合这个要求)。
  • 如果需要适配更特殊的Runner(比如Parameterized),可以在MyRunner中添加额外的判断和适配逻辑。
  • 你的MyApi类可以根据需求实现任何自定义逻辑,比如记录测试耗时、清理测试数据、发送测试通知等等。

内容的提问来源于stack exchange,提问作者bobmarksie

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 04:06:14