自定义注解过滤JUnit测试类

来源:互联网 发布:linux中man的用法 编辑:程序博客网 时间:2024/06/13 01:28

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:

@Overridepublic 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 {@Overridepublic Runner runnerForClass(Class<?> testClass) throws Throwable {return new BlockJUnit4ClassRunner(testClass);}}

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

public class IgnoredBuilder extends RunnerBuilder {@Overridepublic 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同名文件的源码,并修改/添加其中如下部分:

@Overridepublic 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 {@Overridepublic 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;} elsereturn "";}}

相应的,MyBuilder可修改为:

public class MyBuilder extends RunnerBuilder {@Overridepublic 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);}}