美文网首页
spring单元测试中的设计模式和面试高级问题

spring单元测试中的设计模式和面试高级问题

作者: 提米锅锅 | 来源:发表于2019-10-07 01:09 被阅读0次

junit单元测试属于是人都会用的东西,面试被问的不多,但是如何你能聊点高级点的知识,肯定会让面试官刮目相看。

比如下面几个问题,不是每个人都可以脱口而出。
1 junit测试函数可以带参数吗
2 单元测试架构用了什么设计模式
3 指定RunWith参数的作用是什么
4 @After和@Before标签的实现原理

junit里测试由runner开始,不同runner满足不同的测试需求,比如SuitRunner可以同时执行多个测试,而默认的runner只能每次执行一个测试。

通过debug我们可以跟到ClassRequest类:


ScreenClip.png

这里会返回一个匹配的runner,主要逻辑在AllDefaultPossibilitiesBuilder里。

  @Override
    public Runner getRunner() {
        if (runner == null) {
            synchronized (runnerLock) {
                if (runner == null) {
                    runner = new AllDefaultPossibilitiesBuilder(canUseSuiteMethod).safeRunnerForClass(fTestClass);
                }
            }
        }
        return runner;
    }

AllDefaultPossibilitiesBuilder的runnerForClass会去从预设的RunnerBuilder匹配,只要找到一个合适的runner就会返回。不难猜到,annotatedBuilder正是用于处理@RunWith标签指定了runner的情况,如果没有匹配的runner,会用JUnit4Builder提供的BlockJUnit4ClassRunner。

 @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;
    }

annotatedBuilder的runnerForClass方法实现

    @Override
    public Runner runnerForClass(Class<?> testClass) throws Exception {
        for (Class<?> currentTestClass = testClass; currentTestClass != null;
             currentTestClass = getEnclosingClassForNonStaticMemberClass(currentTestClass)) {
            //寻找类的注解
            RunWith annotation = currentTestClass.getAnnotation(RunWith.class);
            if (annotation != null) {
                return buildRunner(annotation.value(), testClass);
            }
        }

        return null;
    }
public class JUnit4Builder extends RunnerBuilder {
    @Override
    public Runner runnerForClass(Class<?> testClass) throws Throwable {
        return new BlockJUnit4ClassRunner(testClass);
    }
}

继续debug到了JUnitCore,从作者注释可知,JUnitCore是测试门面类,它包装了从不同入口进行测试的方法,同时实现了测试的主框架。

/**
 * <code>JUnitCore</code> is a facade for running tests. It supports running JUnit 4 tests,
 * JUnit 3.8.x tests, and mixtures. To run tests from the command line, run
 * <code>java org.junit.runner.JUnitCore TestClass1 TestClass2 ...</code>.
 * For one-shot test runs, use the static method {@link #runClasses(Class[])}.
 * If you want to add special listeners,
 * create an instance of {@link org.junit.runner.JUnitCore} first and use it to run the tests.
 *

这个函数是给命令行调用的。

 public static void main(String... args) {
        Result result = new JUnitCore().runMain(new RealSystem(), args);
        System.exit(result.wasSuccessful() ? 0 : 1);
    }

不管哪个入口,最终都会走到runner,这段代码可以看出junit的设计采用了观察者模式,junit的listener负责接受测试执行阶段的各种事件,并执行自己的处理逻辑,listener注册给notifer,notifer负责在特定阶段通知所有的listener。

    public Result run(Runner runner) {
        Result result = new Result();
        //ResultListener用于收集测试结果
        RunListener listener = result.createListener();
        notifier.addFirstListener(listener);
        try {
           //会调用result的testRunStarted
            notifier.fireTestRunStarted(runner.getDescription());
          //执行测试
            runner.run(notifier);
          //会调用result的testRunFinished
            notifier.fireTestRunFinished(result);
        } finally {
            removeListener(listener);
        }
        return result;
    }

查看下Result类定义可知,这个类是用来收集测试结果,比如打印出耗时,多少成功失败等。

public class Result implements Serializable {
    /** Only set during deserialization process. */
    private SerializedForm serializedForm;

    public Result() {
        count = new AtomicInteger();
        ignoreCount = new AtomicInteger();
        failures = new CopyOnWriteArrayList<Failure>();
        runTime = new AtomicLong();
        startTime = new AtomicLong();
    }

往下翻可以发现result内置的listener,用于接收测试开始,完成等事件的回调,这里在testRunStarted的时候保存starttime,在testRunFinished记录了endTime 。

  @RunListener.ThreadSafe
    private class Listener extends RunListener {
        @Override
        public void testRunStarted(Description description) throws Exception {
            startTime.set(System.currentTimeMillis());
        }

        @Override
        public void testRunFinished(Result result) throws Exception {
            long endTime = System.currentTimeMillis();
            runTime.addAndGet(endTime - startTime.get());
        }

        @Override
        public void testFinished(Description description) throws Exception {
            count.getAndIncrement();
        }

调到resultListener里记录开始测试的时间

    @RunListener.ThreadSafe
    private class Listener extends RunListener {
        @Override
        public void testRunStarted(Description description) throws Exception {
            startTime.set(System.currentTimeMillis());
        }

测试完成后,记录测试完成的时间

    @RunListener.ThreadSafe
        @Override
        public void testRunFinished(Result result) throws Exception {
            long endTime = System.currentTimeMillis();
            runTime.addAndGet(endTime - startTime.get());
        }

methodBlock函数负责把@Before和@After组装到statement上,有点像JDK动态代理的链式简化版,如果同时有after和before,那么会先调用beofore,再执行测试方法,最后调用after代码,类似advise,这是由不同Statement代码实现的。

  protected Statement methodBlock(FrameworkMethod method) {
        Object test;
        try {
            test = new ReflectiveCallable() {
                @Override
                protected Object runReflectiveCall() throws Throwable {
                    return createTest();
                }
            }.run();
        } catch (Throwable e) {
            return new Fail(e);
        }

        Statement statement = methodInvoker(method, test);
        statement = possiblyExpectingExceptions(method, test, statement);
        statement = withPotentialTimeout(method, test, statement);
        //获取before注解的方法
        statement = withBefores(method, test, statement);
        //获取after注解的方法
        statement = withAfters(method, test, statement);
        statement = withRules(method, test, statement);
        return statement;
    }
 protected Statement withBefores(FrameworkMethod method, Object target,
            Statement statement) {
        //获取有@before注解的方法
        List<FrameworkMethod> befores = getTestClass().getAnnotatedMethods(
                Before.class);
        return befores.isEmpty() ? statement : new RunBefores(statement,
                befores, target);
    }

RunAfter实现逻辑,RunAfters会把before复制给自己的next属性,先调用next,而RunBefore则会先调用自己的方法,再调用测试方法。

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

    @Override
    public void evaluate() throws Throwable {
        for (FrameworkMethod before : befores) {
            before.invokeExplosively(target);
        }
        next.evaluate();
    }
 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 {
                    each.invokeExplosively(target);
                } catch (Throwable e) {
                    errors.add(e);
                }
            }
        }
        MultipleFailureException.assertEmpty(errors);
    }

后续会继续分析junit和springboot的初始化关系,比如junit如何加载spring环境。

相关文章

网友评论

      本文标题:spring单元测试中的设计模式和面试高级问题

      本文链接:https://www.haomeiwen.com/subject/qowypctx.html