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);
}
}
本文介绍了一种方法,通过自定义注解和Builder类来动态控制JUnit测试类的运行。当需要根据特定条件运行或跳过测试类时,避免频繁地添加或移除@Ignore注解。通过分析JUnit源码,创建了名为@MyIgnore的自定义注解,并实现了自己的RunnerBuilder,根据配置文件决定是否运行测试类。
1679

被折叠的 条评论
为什么被折叠?



