JUnit 框架阅读一

Junit 基础元素

最近阅读了JUnit 的源代码,记录一下。首先我画了一张JUnit 基础元素图如下:

junitFirst.png

基本上涵盖了JUnit 的核心组件和类。下面我们会选一部分比较重要的元素来作一些解释。

Runner

首先是 Runner 类,Runner是用来运行测试的主要类,下图是Runner的继承结构:


image.png

其中 ParentRunner是一个比较重要的Runner,里面承载了很多测试业务的代码。读者可以详细阅读 ParentRunner。好了,我们看一下 Runner的抽象基类:

public abstract class Runner implements Describable {
    /*
     * (non-Javadoc)
     * @see org.junit.runner.Describable#getDescription()
     */
    public abstract Description getDescription();

    /**
     * Run the tests for this runner.
     *
     * @param notifier will be notified of events while tests are being run--tests being
     * started, finishing, and failing
     */
    public abstract void run(RunNotifier notifier);

    /**
     * @return the number of tests to be run by the receiver
     */
    public int testCount() {
        return getDescription().testCount();
    }
}

其中的 run 方法是用来跑测试的方法,run 方法中有一个 RunNotifier 的参数,主要是用来通知测试的生命周期我们下面会提到它,以 ParentRunner来作例子,run 方法如下:

public abstract class ParentRunner<T> extends Runner implements Filterable,
        Orderable {
  private final TestClass testClass;
  ...
  @Override
    public void run(final RunNotifier notifier) {
        EachTestNotifier testNotifier = new EachTestNotifier(notifier,
                getDescription());
        testNotifier.fireTestSuiteStarted();
        try {
            Statement statement = classBlock(notifier);
            statement.evaluate();
        } catch (AssumptionViolatedException e) {
            testNotifier.addFailedAssumption(e);
        } catch (StoppedByUserException e) {
            throw e;
        } catch (Throwable e) {
            testNotifier.addFailure(e);
        } finally {
            testNotifier.fireTestSuiteFinished();
        }
    }
    ...
    protected Statement classBlock(final RunNotifier notifier) {
        Statement statement = childrenInvoker(notifier);
        if (!areAllChildrenIgnored()) {
            statement = withBeforeClasses(statement);
            statement = withAfterClasses(statement);
            statement = withClassRules(statement);
            statement = withInterruptIsolation(statement);
        }
        return statement;
    }
    ...
}

在上述代码中 testClass 是类型为 TestClass的对象,包装了被测试类,后面会提到。可以看到,在run方法中实际上是运行 Statement 的evaluate,主要使用了责任链的设计模式,Runner 会创建一系列的 Statement,以BlockJUnit4ClassRunner为例子:

protected Statement methodBlock(final FrameworkMethod method) {
       ...
        Statement statement = methodInvoker(method, test);
        statement = possiblyExpectingExceptions(method, test, statement);
        statement = withPotentialTimeout(method, test, statement);
        statement = withBefores(method, test, statement);
        statement = withAfters(method, test, statement);
        statement = withRules(method, test, statement);
        statement = withInterruptIsolation(statement);
        return statement;
    }

可以看到我们测试时候常用的一些生命周期,比如 @Before, @After 以及我们在测试中设置的一些 @get:Rule,都会通过解析Annotation之后生成 statement 来串行的运行。

TestClass

TestClass 实现 Annotatable 接口,提供方法校验和注解搜索的功能。

public interface Annotatable {
    /**
     * Returns the model elements' annotations.
     */
    Annotation[] getAnnotations();

    /**
     * Returns the annotation on the model element of the given type, or @code{null}
     */
    <T extends Annotation> T getAnnotation(Class<T> annotationType);
}

Runner 会invoke其中的getAnnotatedMethods来获取被测试类中的标注方法。以 @BeforeClass 标注为例子:在运行 Runner 的 run 方法时,会调用withBeforeClasses 函数如下:

 protected Statement withBeforeClasses(Statement statement) {
        List<FrameworkMethod> befores = testClass
                .getAnnotatedMethods(BeforeClass.class);
        return befores.isEmpty() ? statement :
                new RunBefores(statement, befores, null);
    }

在这个函数里调用了 getAnnotatedMethods 方法参数是 BeforeClass.class,这样就获取到了被测试类中被 @BeforeClass标注的方法, 然后将其封装在RunBefores类中, RunBefores 是一个Statement的子类其中的evaluate方法会调用个测试类中被@BeforeClasse 的方法。

RunNotifier

RunNotifier 是负责通知测试的生命周期回调。其中有一个私有的抽象内部类 SafeNotifier. 并且提供了一系列的生命周期回调方法,在对应的时机调用:

  public class RunNotifier {
    private final List<RunListener> listeners = new CopyOnWriteArrayList<RunListener>();
    ...
    public void fireTestRunFinished(final Result result) {
        new SafeNotifier() {
            @Override
            protected void notifyListener(RunListener each) throws Exception {
                each.testRunFinished(result);
            }
        }.run();
    }
    ...
    public void fireTestSuiteStarted(final Description description) {
        new SafeNotifier() {
            @Override
            protected void notifyListener(RunListener each) throws Exception {
                each.testSuiteStarted(description);
            }
        }.run();
    }
 }

调用位置以JUnitCore为例子: notifier.fireTestRunStarted(runner.getDescription());

     public Result run(Runner runner) {
        Result result = new Result();
        RunListener listener = result.createListener();
        notifier.addFirstListener(listener);
        try {
            notifier.fireTestRunStarted(runner.getDescription());
            runner.run(notifier);
            notifier.fireTestRunFinished(result);
        } finally {
            removeListener(listener);
        }
        return result;
    }

我们可以看到在开始运行测试的时候,通过调用 notifier 的 fireTestRunStarted 来通知测试开始。

Statement

Statement是一个抽象基类:有一些具体实现类比如 RunAfters, RunRules.

public abstract class Statement {
    /**
     * Run the action, throwing a {@code Throwable} if anything goes wrong.
     */
    public abstract void evaluate() throws Throwable;
}

我们以RunAfters 为例子:

public class RunAfters extends Statement {
   private final Statement next;

   private final Object target;

   private final List<FrameworkMethod> afters;

   public RunAfters(Statement next, List<FrameworkMethod> afters, Object target) {
       this.next = next;
       this.afters = afters;
       this.target = target;
   }

   @Override
   public void evaluate() throws Throwable {
       List<Throwable> errors = new ArrayList<Throwable>();
       try {
           next.evaluate();
       } catch (Throwable e) {
           errors.add(e);
       } finally {
           for (FrameworkMethod each : afters) {
               try {
                   invokeMethod(each);
               } catch (Throwable e) {
                   errors.add(e);
               }
           }
       }
       MultipleFailureException.assertEmpty(errors);
   }

   /**
    * @since 4.13
    */
   protected void invokeMethod(FrameworkMethod method) throws Throwable {
       method.invokeExplosively(target);
   }
}

其中 next 指向责任链的下一个节点。不难看出 afters 方法在 finally 代码块中执行,即无论测试是失败还是成功都会run @After 标注的代码。

TestRule

我们在测试的时候经常会遇到需要使用 TestRule 的情况:
比如在测试koltin 的协程代码的时候就会遇到需要添加自定义的Rule:

@get:Rule
    val testCoroutineRule = TestCoroutineRule()

自定义或者JUnit 内部定义的一系例Rule 都是继承TestRule 这个接口:

public interface TestRule {
    /**
     * Modifies the method-running {@link Statement} to implement this
     * test-running rule.
     *
     * @param base The {@link Statement} to be modified
     * @param description A {@link Description} of the test implemented in {@code base}
     * @return a new statement, which may be the same as {@code base},
     *         a wrapper around {@code base}, or a completely new Statement.
     */
    Statement apply(Statement base, Description description);
}

其中的 apply 方法是用来基于 base Statement 加上测试Rule 来创建一个新的Statement用于evaluate。举个例子Verifier:

   public abstract class Verifier implements TestRule {
   public Statement apply(final Statement base, Description description) {
       return new Statement() {
           @Override
           public void evaluate() throws Throwable {
               base.evaluate();
               verify();
           }
       };
   }

   /**
    * Override this to add verification logic. Overrides should throw an
    * exception to indicate that verification failed.
    */
   protected void verify() throws Throwable {
   }
}

我们看到在 Verifier 的 apply 方法中首先调用了 baseStatement的 evaluate 方法,然后调用自己 verify 方法。如何使用呢?我们看一下测试例子。

  public class VerifierRuleTest {

    private static String sequence;

    public static class UsesVerifier {
        @Rule
        public Verifier collector = new Verifier() {
            @Override
            protected void verify() {
                sequence += "verify ";
            }
        };

        @Test
        public void example() {
            sequence += "test ";
        }
    }

    @Test
    public void verifierRunsAfterTest() {
        sequence = "";
        assertThat(testResult(UsesVerifier.class), isSuccessful());
        assertEquals("test verify ", sequence);
    }
}

我们看到测试例子中 assertEquals("test verify ", sequence); 是通过的,结合 Verifier 的代码,我们可以看到verifier 在测试代码运行之后调用。

JUnit的框架代码不是很多,很适合新手开始阅读,所以我的一系列框架阅读之旅就从简单的开始,这一篇是JUnit的基础篇,下一篇会讲到一些具体的使用情况。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容