zoukankan      html  css  js  c++  java
  • 简单易懂, JUnit 框架问答

    本文算是一个关于Junit4相关的知识分享,但是不同于网上大段的源码分析,模式学习文章,我想通过问答的形式,引出代码来简明阐述JUnit4是如何实现需要的功能的。
    考虑到任何一个框架,都是为了解决问题而存在的。那么我想,带着问题去看源码会不会事半功倍呢?
    Note:本文基于Junit4.11源码

    Junit4怎么就能跑Case?

    众所周知,JUnit框架能帮助跑单元测试的Case,那么它到底是如何实现的呢?换句话说,如果没有JUnit,我们会怎么执行Case?
    OK,很简单,一个case就是一个方法,那要想执行他们,直接在Main方法里调用就可以了。
    但是当Case很多时,我们就得一个一个在Main方法里显示的调用Case。
    虽然稍显繁琐,还是可以解决问题的,不过拓展性就不敢恭维了,别人也没办法使用我们已写的东西?!

    那么,如果要上升到框架层面,能够让更多的人使用,该怎么做呢? Junit给了我们很好的例子.

    好的单元测试框架一定是最大限度的方便使用者,让其尽可能只关注单元测试本身,而不是其他一些冗余的事情.
    很明显Junit做到了这一点, 它不需要你去显示的自己调用Case,一切都帮你做好,你只要告诉它要测哪个类就好了,以JUnit4.11默认的Runner为例:

    BlockJUnit4ClassRunner aRunner = new BlockJUnit4ClassRunner(JunitTest.class);
      aRunner.run(new RunNotifier());
    

    Debug进去,我们就会发现,原来JUnit是用反射机制来跑我们写的Case。

    public class InvokeMethod extends Statement {
        private final FrameworkMethod fTestMethod;
        private Object fTarget;
    
        public InvokeMethod(FrameworkMethod testMethod, Object target) {
            fTestMethod = testMethod;
            fTarget = target;
        }
    
        @Override
        public void evaluate() throws Throwable {
            fTestMethod.invokeExplosively(fTarget);
        }
    }
    

    这里的InvokeMethod就是实际执行方法的类,跟进去方法:fTestMethod.invokeExplosively(fTarget);
    我们就会看到Method.invoke(obj,args)的最终反射调用。

        public Object invokeExplosively(final Object target, final Object... params)
            throws Throwable {
            return new ReflectiveCallable() {
                @Override
                protected Object runReflectiveCall() throws Throwable {
                    return fMethod.invoke(target, params);
                }
            }.run();
        }
    

    如何实现 @Test 标记功能?

    Junit在4.0以后进行了一个大的版本变动,引入了Java 1.5的注解功能,就像我们常用的 @Test, @BeforeClass, @AfterClass 一样,那么这些功能是如何实现的呢?
    以 @Test 为例:

     @Retention(RetentionPolicy.RUNTIME)
     @Target({ElementType.METHOD})
     public @interface Test {
        static class None extends Throwable {
            private static final long serialVersionUID = 1L;
    
            private None() {
            }
        }
        Class<? extends Throwable> expected() default None.class;
        long timeout() default 0L;
    }
    

    它以RetentionPolicy.RUNTIME标记, 就表示这个注解在运行时仍然会被JVM保存,因此就可以通过反射得到它:

    protected List<FrameworkMethod> computeTestMethods() {
        return getTestClass().getAnnotatedMethods(Test.class);
    }
    

    这样JUnit就得到了所有你要跑的Case。

    而对于 @BeforeClass, @AfterClass 都是一个原理。

    Junit4如何判断结果?

    上面说到如何找Case,如何跑Case,那么对于一个完成的执行流程,我们还缺一块:你的Case是Pass还是Fail?
    比如下面:

    org.junit.Assert.assertTrue("check assert method", false);
    

    我们通常是用Assert去做断言的,还有其他的方法,比如assertEqulas,assertNotEqulas。显而易见,上面的断言结果一定是Fail的,那具体是如何实现的呢?
    要解开谜题,我们先看看Assert.assertTrue(String message, boolean condition)方法的具体实现:

    static public void assertTrue(String message, boolean condition) {
        if (!condition) {
            fail(message);
        }
    }
    
    static public void fail(String message) {
        if (message == null) {
            throw new AssertionError();
        }
        throw new AssertionError(message);
    }
    

    当condition是false时,它会抛一个java.lang.AssertionError异常。这个类是从Java 1.4引入的,并且它跟所有的Java的异常一样都是从Throwable继承来的。
    那Junit如何捕获呢?
    org.junit.runners.ParentRunner<T>类我们找到了答案:

    protected final void runLeaf(Statement statement, Description description,
            RunNotifier notifier) {
        EachTestNotifier eachNotifier = new EachTestNotifier(notifier, description);
        eachNotifier.fireTestStarted();
        try {
            statement.evaluate();
        } catch (AssumptionViolatedException e) {
            eachNotifier.addFailedAssumption(e);
        } catch (Throwable e) {
            eachNotifier.addFailure(e);
        } finally {
            eachNotifier.fireTestFinished();
        }
    }
    

    这个方法目的就是执行单个Case,它是用Try Catch来捕获所有非预期的异常错误。
    只要Catch到Throwable异常了,很明显Case就挂了。反之,Case则是pass的。

    这里还可以继续深入,比如我们知道,@Test 有一个expected参数,可以让我们填入期望捕获的异常,那么JUnit如何实现的呢?

    总结

    好了,我们来看看我们都说了啥:

    • Junit通过 @Test 注解,找到我们想跑的Case
    • 通过反射,来执行我们的Case
    • 最后通过Catch Throwable exception来判断我们的case 是Pass还是Fail

    很明显JUnit还有很多其他的功能,比如TestSuite,Rule,各种Runners,这里不在一一罗列了。
    我想,作为表达一条Case执行的主线,讲到这里应该已经足够了,不是嘛?

    如果您看了本篇博客,觉得对您有所收获,请点击下面的 [推荐]
    如果您想转载本博客,请注明出处大卡的博客[http://www.cnblogs.com/jinsdu/]
    如果您对本文有意见或者建议,欢迎留言

  • 相关阅读:
    15、集合--TreeSet的源码分析(待完成)
    13、集合--HashSet相关方法源码解析(等map更新完成之后在进行补充)
    11、集合--Set接口
    10、集合--Set、AbstractSet、HashSet、TreeSet、SortedSet源码
    9、集合--ArrayList和LinkedList的一些对比
    8、集合--LinkedList的测试以及相关方法的源码分析
    7、集合--ArrayList的测试以及相关方法的源码解析
    6、集合--List接口
    Linux 高可用(HA)集群之keepalived详解
    CentOS7安装配置redis-3.0.0
  • 原文地址:https://www.cnblogs.com/jinsdu/p/4456824.html
Copyright © 2011-2022 走看看