自定义注解过滤JUnit测试类

本文介绍了一种方法,通过自定义注解和Builder类来动态控制JUnit测试类的运行。当需要根据特定条件运行或跳过测试类时,避免频繁地添加或移除@Ignore注解。通过分析JUnit源码,创建了名为@MyIgnore的自定义注解,并实现了自己的RunnerBuilder,根据配置文件决定是否运行测试类。
部署运行你感兴趣的模型镜像

JUnit是常用单元测试工具,如果希望跳过某个测试类,一般在类上面添加@Ignore注解。实际情况下,经常遇到某些测试类在符合某些条件时需要运行、不符合时又不需要运行的情况,频繁加减@Ignore注解的话相当繁琐。有没有办法,能根据自己的配置文件,灵活决定是否运行某些测试类呢?

首先来分析一下JUnit源码(以4.10版本为例)。在org.junit.runner包下,有个JUnitCore.class,其中的main方法就是JUnit入口函数。经过runMainAndExit->runMain->run的多次调用,发现在run之中通过Request.classes方法构建了AllDefaultPossibilitiesBuilder对象,该对象用于选择RunnerBuilder,继而选择Runner执行测试用例。源码如下:

public static Request classes(Computer computer, Class<?>... classes) {
		try {
			AllDefaultPossibilitiesBuilder builder= new AllDefaultPossibilitiesBuilder(true);
			Runner suite= computer.getSuite(builder, classes);
			return runner(suite);
		} catch (InitializationError e) {
			throw new RuntimeException(
					"Bug in saff's brain: Suite constructor, called as above, should always complete");
		}
	}

在AllDefaultPossibilitiesBuilder中有个runnerForClass方法,就是该方法选择了RunnerBuilder,并通过调用RunnerBuilder的runnerForClass方法,最终决定了Runner:

@Override
	public Runner runnerForClass(Class<?> testClass) throws Throwable {
		List<RunnerBuilder> builders= Arrays.asList(
				ignoredBuilder(),
				annotatedBuilder(),
				suiteMethodBuilder(),
				junit3Builder(),
				junit4Builder());

		for (RunnerBuilder each : builders) {
			Runner runner= each.safeRunnerForClass(testClass);
			if (runner != null)
				return runner;
		}
		return null;
	}

从上述代码可以看出,正常情况下会选择JUnit4Builder,其源码如下:

public class JUnit4Builder extends RunnerBuilder {
	@Override
	public Runner runnerForClass(Class<?> testClass) throws Throwable {
		return new BlockJUnit4ClassRunner(testClass);
	}
}

而一旦对类添加了@Ignore注解,则会选择IgnoredBuilder,其源码如下:

public class IgnoredBuilder extends RunnerBuilder {
	@Override
	public Runner runnerForClass(Class<?> testClass) {
		if (testClass.getAnnotation(Ignore.class) != null)
			return new IgnoredClassRunner(testClass);
		return null;
	}
}


看到这里,我们大体可以认为,BlockJUnit4ClassRunner(testClass)会正常运行测试类,而IgnoredClassRunner(testClass)则会跳过运行测试类。因此,对于开始提出的问题,可以用如下方法解决:构建自己的Builder类,在其中的runnerForClass中根据配置决定是否运行测试类。

由于IgnoredBuilder中是通过读取类注解的方法,我们不妨类似定义自己的Ignore注解:

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE })
public @interface MyIgnore {
	String value() default "";
}

然后,新建与JUnit源码中同名的包org.junit.internal.builders,并在其中新建同名文件AllDefaultPossibilitiesBuilder,拷贝进来JUnit同名文件的源码,并修改/添加其中如下部分:

@Override
public Runner runnerForClass(Class<?> testClass) throws Throwable {
		List<RunnerBuilder> builders= Arrays.asList(
				ignoredBuilder(),
				myBuilder(),
				annotatedBuilder(),
				suiteMethodBuilder(),
				junit3Builder(),
				junit4Builder());

		for (RunnerBuilder each : builders) {
			Runner runner= each.safeRunnerForClass(testClass);
			if (runner != null)
				return runner;
		}
		return null;
	}
protected MyBuilder myBuilder() {
		return new MyBuilder();
	}

并在这个包下,定义自己的Builder:

public class MyBuilder extends RunnerBuilder {
	@Override
	public Runner runnerForClass(Class<?> testClass) throws Throwable {
		if (testClass.getAnnotation(MyIgnore.class) != null) {
			if (...) //自定义过滤条件
				return new IgnoredClassRunner(testClass);
		}
		return new BlockJUnit4ClassRunner(testClass);
	}
}

这样,只要在测试类上添加自定义注解@MyIgnore,即可根据自定义过滤条件决定该类运行与否。


PS:编写测试类时,一般是在基类中读取配置文件,并为某些成员赋值。而在MyBuilder中传入基类的成员变量作为过滤条件是不行的,因为MyBuilder的运行还在测试基类之前,此时基类的任何变量都为null。在MyBuilder中自定义过滤条件的变量,必须在MyBuilder中从配置文件里实时读取。由于基类也需要加载配置文件,为避免重复加载,建议将读配置类实现为单例模式:

public class MyConfiguration {
	private volatile static MyConfiguration uniqueInstance;

	private Properties propertie;
	private FileInputStream inputFile;

	private MyConfiguration() {
		propertie = new Properties();
	}

	private MyConfiguration(String configFile) {
		propertie = new Properties();
		try {
			inputFile = new FileInputStream(configFile);
			propertie.load(inputFile);
			inputFile.close();
		} catch (FileNotFoundException ex) {
			System.out
					.println("load properties file failed! maybe file not exist!");
			ex.printStackTrace();
		} catch (IOException ex) {
			System.out.println("load properties file failed!");
			ex.printStackTrace();
		}
	}

	/**
	 * @return unique instance
	 */
	public static MyConfiguration getInstance(String filePath) {
		if (uniqueInstance == null) {
			synchronized (MyConfiguration.class) {
				if (uniqueInstance == null) {
					uniqueInstance = new MyConfiguration(filePath);
				}
			}
		}

		return uniqueInstance;
	}

	public String getValue(String key) {
		if (propertie.containsKey(key)) {
			String value = propertie.getProperty(key);
			return value;
		} else
			return "";
	}

}

相应的,MyBuilder可修改为:

public class MyBuilder extends RunnerBuilder {
	@Override
	public Runner runnerForClass(Class<?> testClass) throws Throwable {
		String properties = "src/test/resources/test.properties";
		MyConfiguration conf = MyConfiguration.getInstance(properties);

		if (testClass.getAnnotation(MyIgnore.class) != null) {
			if (!"run".equals(conf.getValue("runFlag")))
				return new IgnoredClassRunner(testClass);
		}
		return new BlockJUnit4ClassRunner(testClass);
	}
}

 

您可能感兴趣的与本文相关的镜像

Stable-Diffusion-3.5

Stable-Diffusion-3.5

图片生成
Stable-Diffusion

Stable Diffusion 3.5 (SD 3.5) 是由 Stability AI 推出的新一代文本到图像生成模型,相比 3.0 版本,它提升了图像质量、运行速度和硬件效率

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值