`
longgangbai
  • 浏览: 7330338 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

JUnit4.1源代码分析

阅读更多

用JUnit4进行测试有两种方式分别是:

  (1)、命令行方式:java org.junit.runner.JUnitCore [java class...];

  (2)、程序方式:直接调用org.junit.runner.JUnitCore.runClass(Class<?>...clazz)方法;

这两种测试的方法,最终调用的是同一个执行体。先看第一种测试方法:在JUnitCore这个类中,我们可以发现它有一个main方法:

1 public static void main(String... args) {
2     runMainAndExit(new RealSystem(), args);
3 }

这就是命令行方式执行的入口,JUnitCore.runMain()方法

复制代码
 1 public Result runMain(JUnitSystem system, String... args) {
 2         system.out().println("JUnit version " + Version.id());
 3         List<Class<?>> classes= new ArrayList<Class<?>>();
 4         List<Failure> missingClasses= new ArrayList<Failure>();
 5         for (String each : args)
 6             try {
 7                 classes.add(Class.forName(each));
 8             } catch (ClassNotFoundException e) {
 9                 system.out().println("Could not find class: " + each);
10                 Description description= Description.createSuiteDescription(each);
11                 Failure failure= new Failure(description, e);
12                 missingClasses.add(failure);
13             }
14         RunListener listener= new TextListener(system);
15         addListener(listener);
16         Result result= run(classes.toArray(new Class[0]));
17         for (Failure each : missingClasses)
18             result.getFailures().add(each);
19         return result;
20     }
复制代码

在上面这个方法中,主要是构造一个存储Class<?>类型的List,以供JUnitCore.run(Class<?>...clazz);来执行。

再看第二种执行方式,调用的是JUnitCore.runClass(Class<?>... classes),

1     public static Result runClasses(Class<?>... classes) {
2         return new JUnitCore().run(defaultComputer(), classes);
3     }

在这个方法里面,会构造一个默认的Computer(这个后面会解释它的用处),紧接着就调用了JUnitCore的另外一个重载的run()方法

1   public Result run(Computer computer, Class<?>... classes) {
2         return run(Request.classes(computer, classes));
3    }

该方法会构造一个封装了Runner的Request,然后继续调用JUnitCore的run方法:

复制代码
 1 public Result run(Runner runner) {
 2         Result result= new Result();
 3         RunListener listener= result.createListener();
 4         fNotifier.addFirstListener(listener);
 5         try {
 6             fNotifier.fireTestRunStarted(runner.getDescription());
 7             runner.run(fNotifier);
 8             fNotifier.fireTestRunFinished(result);
 9         } finally {
10             removeListener(listener);
11         }
12         return result;
13     }
复制代码

该方法才是真正的开始执行Test,首先会构造一个Result的对象,顾名思义就知道它是记录运行时的状态,

复制代码
 1 public class Result implements Serializable {
 2     private static final long serialVersionUID = 1L;
 3     private AtomicInteger fCount = new AtomicInteger();
 4     private AtomicInteger fIgnoreCount= new AtomicInteger();
 5     private final List<Failure> fFailures= Collections.synchronizedList(new ArrayList<Failure>());
 6     private long fRunTime= 0;
 7     private long fStartTime;
 8     //.......
 9     private class Listener extends RunListener {
10     //.......
11     }
12 }
复制代码

包括:所运行Test的数量,所忽略Test的数量,开始运行的时间,总运行时间,运行期间出现错误的信息;它里面还包含一个私有内部类,该类的作用就是帮助Result记录状态的。最后会执行Runner的run方法。这时就真正开始执行Test了。

 

这篇主要讲下JUnit是如何构建、运行Runner 。

JUnit4中是通过Request.classes(Computer computer, Class<?>... classes)来构造Runner的

复制代码
 //org.junit.runner.Request
 1 public static Request classes(Computer computer, Class<?>... classes) {
 2         try {
 3             AllDefaultPossibilitiesBuilder builder= new AllDefaultPossibilitiesBuilder(true);
 4             Runner suite= computer.getSuite(builder, classes);
 5             return runner(suite);
 6         } catch (InitializationError e) {
 7             throw new RuntimeException(
 8                     "Bug in saff's brain: Suite constructor, called as above, should always complete");
 9         }
10     }
复制代码

       在上面这个方法中出现了一个重要的类,就是AllDefaultPossibilitiesBuilder,这个类就是用来选择RunerBuilder,继而选择Runner来执行Test。这个类中有一重要的方法runnerForClass(Class<?> testClass)(代码-1)

该方法用来确定选择RunerBuilder,继而选择Runner来执行Test。默认选择的是junit4Builder();(但是你可以自由选择和自己实现,可以用@RunWith Annotation来标注,这样就会选择annotatedBuilder()来作为RunnerBuilder.)。

在 选择好了RunnerBuilder后,接下就是要构建Runner了,这里是用Computer.getSuite()(代码-2)来获取Suite类型的 Runner(通过类层次关系,你会发现Suiter是继承自ParentRunner的,而ParentRunner是实现了Runner接口的)。

 

代码-1

复制代码
    //org.junit.internal.builders.AllDefaultPossibilitiesBuilder
   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;
    }
复制代码

 

代码-2

复制代码
 //org.junit.runners.Computer
 1   public Runner getSuite(final RunnerBuilder builder,
 2             Class<?>[] classes) throws InitializationError {
 3         return new Suite(new RunnerBuilder() {
 4             @Override
 5             public Runner runnerForClass(Class<?> testClass) throws Throwable {
 6                 return getRunner(builder, testClass);
 7             }
 8         }, classes);
 9     }
10 
11     /**
12      * Create a single-class runner for {@code testClass}, using {@code builder}
13      */
14     protected Runner getRunner(RunnerBuilder builder, Class<?> testClass) throws Throwable {
15         return builder.runnerForClass(testClass);
16     }
复制代码

在上面的执行体中,只是new 出一个Suite对象然后返回。在Suite构造函数中接收两个参数(RunnerBuilder,Class<?>[]);仔细研究这段代码,你会发现这个新new出来的RunnerBuilder(第3行)的功能其实就是传进来的builder(第一行),也就是AllDefaultPossibilitiesBuilder对象;这样执行RunnerBuilder.runnerForClass(),方法时就是执行AllDefaultPossibilitiesBuilder.runnerForClass()方法。

 

再进入Suite的构造函数中,你会发现它调用了Runnerbuilder.runners方法,该方法的作用是获取suite的子集合。具体获取的实现就是调用AllDefaultPossibilitiesBuilder.runnerForClass(), 该方法默认会用JUnit4Builder();然后调用JUnit4Builder.runnerForClass();最后返回的是 BlockJUnit4ClassRunner对象,该对象和Suite是平级的也是继承自ParentRunner。(代码-3)演示了这个执行过程:

//org.junit.runners.Suite
1     public Suite(RunnerBuilder builder, Class<?>[] classes) throws InitializationError {
2         this(null, builder.runners(null, classes));
3     }

 

代码-3

复制代码
 //org.junit.runners.model.RunnerBuilder
 1   public List<Runner> runners(Class<?> parent, Class<?>[] children)
 2             throws InitializationError {
 3         addParent(parent);
 4 
 5         try {
 6             return runners(children);
 7         } finally {
 8             removeParent(parent);
 9         }
10     }
11   private List<Runner> runners(Class<?>[] children) {
12         ArrayList<Runner> runners= new ArrayList<Runner>();
13         for (Class<?> each : children) {
14             Runner childRunner= safeRunnerForClass(each);
15             if (childRunner != null)
16                 runners.add(childRunner);
17         }
18         return runners;
19     }
20 
21     public Runner safeRunnerForClass(Class<?> testClass) {
22         try {
23             return runnerForClass(testClass);
24         } catch (Throwable e) {
25             return new ErrorReportingRunner(testClass, e);
26         }
27     }
28   

    //org.junit.internal.builders.AllDefaultPossibilitiesBuilder
29   @Override
30     public Runner runnerForClass(Class<?> testClass) throws Throwable {
31         List<RunnerBuilder> builders= Arrays.asList(
32                 ignoredBuilder(),
33                 annotatedBuilder(),
34                 suiteMethodBuilder(),
35                 junit3Builder(),
36                 junit4Builder());
37 
38         for (RunnerBuilder each : builders) {
39             Runner runner= each.safeRunnerForClass(testClass);
40             if (runner != null)
41                 return runner;
42         }
43         return null;
44     }

45   //org.junit.internal.builders.AllDefaultPossibilitiesBuilder
46   protected JUnit4Builder junit4Builder() {
47         return new JUnit4Builder();
48     }
49   public Runner runnerForClass(Class<?> testClass) throws Throwable {
50         return new BlockJUnit4ClassRunner(testClass);
51     }
复制代码

构造完上面的子集合以后,就进行到Suite的另外一个构造函数中

//org.junit.runners.Suite
1   protected Suite(Class<?> klass, List<Runner> runners) throws InitializationError {
2         super(klass);
3         fRunners = runners;
4     }

该构造函数中主要的功能就是调用父类的构造函数,在父类的构造函数中主要有以下功能:

1)、进行了部分方法的的验证,包括对BeforeClass和AfterClass所注解得方法进行了public static void 和无参数的判断和一些规则的判断。

2)、构造了一个TestClass(代码-4),这个类对Class对象进行了解析,把目标Class的方法和成员变量都解析出来了。并建立了一个从Annotation到方法和Annotation到成员变量的对应Map。

 

下面是ParentRunner的构造函数:

复制代码
//org.junit.runners.ParentRunner
1   protected ParentRunner(Class<?> testClass) throws InitializationError {
2         fTestClass= new TestClass(testClass);
3         validate();
4     }
1   private void validate() throws InitializationError {
2         List<Throwable> errors= new ArrayList<Throwable>();
3         collectInitializationErrors(errors);
4         if (!errors.isEmpty())
5             throw new InitializationError(errors);
6     }
1   protected void collectInitializationErrors(List<Throwable> errors) {
2         validatePublicVoidNoArgMethods(BeforeClass.class, true, errors);
3         validatePublicVoidNoArgMethods(AfterClass.class, true, errors);
4         validateClassRules(errors);
5     }
复制代码

 

代码-4

复制代码
//org.junit.runners.model.TestClass       
    //Annotation到方法的对应
 1    private Map<Class<?>, List<FrameworkMethod>> fMethodsForAnnotations= new HashMap<Class<?>, List<FrameworkMethod>>();
 2     //Annotation到成员变量的对应
 3     private Map<Class<?>, List<FrameworkField>> fFieldsForAnnotations= new HashMap<Class<?>, List<FrameworkField>>();
 4 
 5     public TestClass(Class<?> klass) {
 6         fClass= klass;
 7         if (klass != null && klass.getConstructors().length > 1)
 8             throw new IllegalArgumentException(
 9                     "Test class can only have one constructor");
10 
11         for (Class<?> eachClass : getSuperClasses(fClass)) {
12             for (Method eachMethod : eachClass.getDeclaredMethods())
13                 addToAnnotationLists(new FrameworkMethod(eachMethod),
14                         fMethodsForAnnotations);
15             for (Field eachField : eachClass.getDeclaredFields())
16                 addToAnnotationLists(new FrameworkField(eachField),
17                         fFieldsForAnnotations);
18         }
19     }
复制代码

这样就成功构造了一个Runner对象(也就是Suite对象),接下了就是运行该Runner了。

复制代码
 1   public Result run(Runner runner) {
 2         Result result= new Result();
 3         RunListener listener= result.createListener();
 4         fNotifier.addFirstListener(listener);
 5         try {
 6             fNotifier.fireTestRunStarted(runner.getDescription());
 7             runner.run(fNotifier);
 8             fNotifier.fireTestRunFinished(result);
 9         } finally {
10             removeListener(listener);
11         }
12         return result;
13     }
复制代码

 

 上面这个方法就是负责启动执行Runner的,他调用Runner.run()方法。也就是ParentRunner(Suite继承自ParentRunner,而Sutie没有对run方法进行重载)的run()方法。

再跟进到ParentRunner.run()方法中,这个方法的功能有:

1、构造一个链式的Statement,分别是:

RunRules --> RunAfters(@AfterClass) -->RunBefores(@BeforeClass) -->

(RunRules--> RunAfters(@After) -->RunBefores(@Before)-->FailOnTimeout(@Test(timeout=?))-->ExpectException(@Test(expect=?))-->InvokeMethod(@Test)...)-->

....

(RunRules--> RunAfters(@After) -->RunBefores(@Before)-->FailOnTimeout(@Test(timeout=?))-->ExpectException(@Test(expect=?))-->InvokeMethod(@Test)...);

从这个链式的结构中可以看出,最先执行的是RunRules这个Statement,然后是RunAfter(这里不要有疑问为什么先执行RunAfter,进去看看实现吧(代码2.1会揭晓一切的))...;

2、记录错误信息,在run方法中会传递进来一个RunNotifier对象,在这里RunNotifier可以拥有多个RunListener。默认的情况下在JunitCore里的RunNotifier只包含Result.RunListener对象,这样就可以进行对Result的状态的改变从而记录运行结果信息,其实这就是典型的观察者模式。RunNotifier就是观察者,得到通知就通知其子集合区执行相应的操作。

复制代码
 1   @Override
 2     public void run(final RunNotifier notifier) {
 3         EachTestNotifier testNotifier= new EachTestNotifier(notifier,
 4                 getDescription());
 5         try {
 6             Statement statement= classBlock(notifier);
 7             statement.evaluate();
 8         } catch (AssumptionViolatedException e) {
 9             testNotifier.fireTestIgnored();
10         } catch (StoppedByUserException e) {
11             throw e;
12         } catch (Throwable e) {
13             e.printStackTrace();
14             testNotifier.addFailure(e);
15         }
16     }
复制代码
1   protected Statement classBlock(final RunNotifier notifier) {
2         Statement statement= childrenInvoker(notifier);
3         statement= withBeforeClasses(statement);
4         statement= withAfterClasses(statement);
5         statement= withClassRules(statement);
6         return statement;
7     }
复制代码

 

复制代码

 

 

 代码-2.1(RunAfter会先执行它的下一个,等它的下一个执行完成之后才执行本体。这样就可以保证呗@After和@AfterClass的注解的方法一定会被执行,即使某个环节执行过程出错也会执行finally的RunAfter本体);

 

复制代码
 1 public class RunAfters extends Statement {
 2     private final Statement fNext;
 3 
 4     private final Object fTarget;
 5 
 6     private final List<FrameworkMethod> fAfters;
 7     
 8     public RunAfters(Statement next, List<FrameworkMethod> afters, Object target) {
 9         fNext= next;
10         fAfters= afters;
11         fTarget= target;
12     }
13 
14     @Override
15     public void evaluate() throws Throwable {
16         List<Throwable> errors = new ArrayList<Throwable>();
17         try {
18             fNext.evaluate();
19         } catch (Throwable e) {
20             errors.add(e);
21         } finally {
22             for (FrameworkMethod each : fAfters)
23                 try {
24                     each.invokeExplosively(fTarget);
25                 } catch (Throwable e) {
26                     errors.add(e);
27                 }
28         }30         MultipleFailureException.assertEmpty(errors);
31     }
32 }
复制代码

 

 

这样JUint的执行流程就讲完了。。。

分享到:
评论

相关推荐

    junit-4.1.jar包,在项目中直接导入即可。

    同时,为了更好地组织和管理测试,通常会将测试类放在与源代码对应的"test"目录下。 总的来说,JUnit 4.1是一个强大的工具,它简化了单元测试的编写和维护,提高了软件开发的质量和可靠性。通过熟练掌握JUnit,...

    struts2最新2.3.4.1源码

    在使用Struts2 2.3.4.1源码时,开发者可以深入理解其内部机制,学习如何定制拦截器、扩展Action、优化配置,以及如何应对安全挑战。同时,对于研究Struts2的发展历程和对比新旧版本的差异也有着重要意义。不过,为了...

    junit-4.1.jar中文-英文对照文档.zip

    源代码下载地址:【***-sources.jar下载地址(官方地址+国内镜像地址).txt】 # 本文件关键字: 中文-英文对照文档,中英对照文档,java,jar包,Maven,第三方jar包,组件,开源组件,第三方组件,Gradle,中文API文档,手册,...

    单元测试利器JUnit4_opt1

    3. 分离测试代码和被测试代码,创建单独的目录,如 "testsrc",并将其添加到项目源代码目录中。测试代码和被测试代码应使用相同的包名,但位于不同的目录下。 编写单元测试: 1. 在测试目录(如 "testsrc")下,...

    cobertura 1.9.4.1

    版本 1.9.4.1 是该工具的一个稳定版本,它为开发者提供了详细的信息,帮助他们了解测试代码对源代码的覆盖范围,从而提升代码质量。 ### Cobertura 的工作原理 Cobertura 在运行 JUnit 或其他单元测试框架时,动态...

    junit 4测试框架培训资料

    - 测试代码和源代码应分开存放,但使用相同的包名,这有利于保持代码结构清晰,同时方便测试。 - 可以创建单独的目录,如`testsrc`,将测试类放入其中。 6. JUnit的最佳实践: - 测试方法应使用`@Test`注解,并保持...

    gradle-4.1.zip

    这对于大型项目来说尤其有益,因为它们通常需要处理大量的源代码文件。此外,Gradle 4.1 还改进了依赖解析,使依赖管理更加高效,减少了不必要的网络请求。 另一个关键特性是支持Kotlin DSL(领域特定语言)作为...

    java及oracle中使用到的jar包

    要使用它,需要在Java代码中配置数据源或创建JDBC连接,并确保将class12.jar添加到项目的类路径中。 2. **dom4j.jar**:dom4j是一个强大的Java库,用于处理XML文档。它提供了灵活的API,可以用来解析、构建、修改和...

    gradle-4.1-all.zip

    10. **文档和源代码**:虽然在发布的“gradle-4.1-all.zip”中,为了减小体积去掉了“docs”和“src”目录,但通常这些目录包含详细的用户指南、API文档以及源代码,这对于学习和调试Gradle是非常有价值的。...

    android 4.0.1系统源码

    系统源码是理解Android运行机制的关键,通过分析源码,开发者可以深入探究其内部工作原理,提升开发效率并优化应用性能。 一、Android系统框架 Android系统主要由五层结构组成:Linux内核、硬件抽象层(HAL)、...

    cobertura-1.9.4.1-src.zip

    这个"cobertura-1.9.4.1-src.zip"压缩包包含的是Cobertura 1.9.4.1版本的源代码。源代码是任何软件开发的基础,它允许开发者深入理解软件的工作原理,并对其进行修改或扩展。 Cobertura的主要功能是跟踪Java程序...

    lesson4.1.zip

    在lesson4.1中,我们可能看到如何配置数据源、定义SQL语句以及事务边界,理解Spring如何进行声明式事务管理。 5. **Spring的Bean Scope**:Spring允许我们定义bean的作用域,如singleton(单例)、prototype(原型...

    Junit_commonly_used_notes.pdf

    - 设置测试用例的相关信息,包括源代码文件夹、测试类名等。 - 完成设置后,Eclipse会自动生成一个包含基本测试框架的测试类。 - **注意事项**: - 自动生成的测试用例仅包含基本框架,需要进一步完善具体的测试...

    cobertura-1.9.4.1-bin.zip

    Cobertura 是一个用于 Java 代码覆盖率测试的工具,它能够帮助开发者测量和跟踪他们的源代码有多少被单元测试覆盖。 在描述 "cobertura-1.9.4.1-bin.zip" 中,没有额外的具体信息,但我们可以推断这可能是一个 ZIP ...

    轻量级Java EE企业应用实战第4版第4章01源代码.rar

    源代码中可能包含JUnit或TestNG编写的测试类,用于验证应用的功能。 8. **Maven或Gradle构建工具**:这些工具用于管理和构建项目,定义依赖关系并自动化构建过程。4.1目录下可能有`.pom.xml`或`build.gradle`文件。...

    spring3.2+hibernate4.1 MVC.zip

    通过分析和学习这个Demo,读者可以深入理解Spring 3.2和Hibernate 4.1的集成方式,以及如何利用MVC模式设计和实现一个Web应用。这个Demo不仅适用于初学者,也为有经验的开发者提供了一种回顾旧版本技术的机会,帮助...

    单元测试junit

    通常做法是在项目根目录下创建一个名为`testsrc`的目录,专门存放测试代码,并确保该目录被添加到项目的源码路径中。 #### 编写单元测试 接下来,我们将通过一个具体的例子来演示如何使用JUnit4进行单元测试。这里...

    Java_lfasr4.1_5f4a5564.zip

    在压缩包Java_lfasr4.1中,可能包含了LFASR(假设这是一个项目名)的源代码、配置文件、文档或其他相关资源。为了深入了解LFASR的功能和使用,我们需要解压文件并查看其中的具体内容,例如类文件、配置文件(如XML...

Global site tag (gtag.js) - Google Analytics