前言

上一节介绍了 https://github.com/houbb/junitperf 的入门应用。

这一节咱们从源码的角度,分析一下其实现形式。

性能测试该怎么做?

Junit Rules

junit4 小伙伴们必定不生疏,那么 junit rules 你听过说过吗?

要想基于 junit4 实现一个性能测试框架,最外围的一点在于了解 Junit Rules。

官网文档:https://github.com/junit-team/junit4/wiki/Rules

Rules 作用

规定容许非常灵活地增加或从新定义测试类中每个测试方法的行为。

测试人员能够重用或扩大上面提供的规定之一,或者编写本人的规定。

自定义规定

ps: 上面的内容来自官网的例子。

大多数自定义规定能够作为 ExternalResource 规定的扩大来实现。

然而,如果您须要无关所探讨的测试类或办法的更多信息,则须要实现 TestRule 接口。

import org.junit.rules.TestRule;import org.junit.runner.Description;import org.junit.runners.model.Statement;public class IdentityRule implements TestRule {  @Override  public Statement apply(final Statement base, final Description description) {    return base;  }}

当然,实现 TestRule 的弱小性能来自应用自定义构造函数的组合、向类增加办法以用于测试,以及将提供的 Statement 包装在新的 Statement 中。

例如,思考以下为每个测试提供命名记录器的测试规定:

package org.example.junit;import java.util.logging.Logger;import org.junit.rules.TestRule;import org.junit.runner.Description;import org.junit.runners.model.Statement;public class TestLogger implements TestRule {  private Logger logger;  public Logger getLogger() {    return this.logger;  }  @Override  public Statement apply(final Statement base, final Description description) {    return new Statement() {      @Override      public void evaluate() throws Throwable {        logger = Logger.getLogger(description.getTestClass().getName() + '.' + description.getDisplayName());        base.evaluate();      }    };  }}

而后这个规定就能够依照上面的形式应用:

import java.util.logging.Logger;import org.example.junit.TestLogger;import org.junit.Rule;import org.junit.Test;public class MyLoggerTest {  @Rule  public final TestLogger logger = new TestLogger();  @Test  public void checkOutMyLogger() {    final Logger log = logger.getLogger();    log.warn("Your test is showing!");  }}

定义和应用

看了下面的例子,咱们发现 junit4 中的自定义规定还是比较简单的。

定义形式:实现 TestRule 接口

应用形式;应用 @Rule 放在创立的外部属性上。

是不是很简略呢?

好了你曾经学会 1+1=2 了,上面让咱们来学习一下泰勒开展吧。

性能测试算法流程

如何统计一个办法的执行耗时呢?

置信你肯定不会生疏,只须要在办法执行开始前和完结后各统计一个工夫,而后差值就是耗时。

如何模仿多个线程调用呢?

应用 java 多线程执行进行模仿即可。

如何生成报告文件呢?

把上述统计的各个维度数据,联合生成对应的 html 等文件即可。

咱们将要做的事件,就是把下面的点综合起来,而后联合 Junit4 Rules 实现即可。

听起来也不难不是吗?

上面,让咱们来一起看一看实现源码吧。

Rule 的入门

入门例子

咱们首先看一个 junit4 的入门例子:

public class HelloWorldTest {    @Rule    public JunitPerfRule junitPerfRule = new JunitPerfRule();    /**     * 繁多线程,执行 1000ms,默认以 html 输入测试后果     * @throws InterruptedException if any     */    @Test    @JunitPerfConfig(duration = 1000)    public void helloWorldTest() throws InterruptedException {        System.out.println("hello world");        Thread.sleep(20);    }}

JunitPerfRule 就是咱们后面提及的自定义规定。

JunitPerfRule

实现如下:

public class JunitPerfRule implements TestRule {    //region private fields    // 省略外部变量    //endregion    @Override    public Statement apply(Statement statement, Description description) {        Statement activeStatement = statement;        JunitPerfConfig junitPerfConfig = description.getAnnotation(JunitPerfConfig.class);        JunitPerfRequire junitPerfRequire = description.getAnnotation(JunitPerfRequire.class);        if (ObjectUtil.isNotNull(junitPerfConfig)) {            // Group test contexts by test class            ACTIVE_CONTEXTS.putIfAbsent(description.getTestClass(), new HashSet<EvaluationContext>());            EvaluationContext evaluationContext = new EvaluationContext(description.getMethodName(), DateUtil.getSimpleDateStr());            evaluationContext.loadConfig(junitPerfConfig);            evaluationContext.loadRequire(junitPerfRequire);            ACTIVE_CONTEXTS.get(description.getTestClass()).add(evaluationContext);            activeStatement = new PerformanceEvaluationStatement(evaluationContext,                    statement,                    statisticsCalculator,                    reporterSet,                    ACTIVE_CONTEXTS.get(description.getTestClass()),                    description.getTestClass()            );        }        return activeStatement;    }}

次要流程就是执行办法的时候,首先获取办法上的 @JunitPerfConfig@JunitPerfRequire 注解信息,而后进行对应的执行统计。

Statement

Statement 是 junit4 中执行最外围的一个对象。

能够发现,这里依据注解信息,对这个实现重写为 PerformanceEvaluationStatement。

PerformanceEvaluationStatement 的外围实现如下:

/** * 性能测试 statement * @author 老马啸东风 * @see com.github.houbb.junitperf.core.rule.JunitPerfRule 用于此规定 */public class PerformanceEvaluationStatement extends Statement {    // 省略外部变量    @Override    public void evaluate() throws Throwable {        List<PerformanceEvaluationTask> taskList = new LinkedList<>();        try {            EvaluationConfig evaluationConfig = evaluationContext.getEvaluationConfig();                        // 依据注解配置,创立对应的执行线程数            for(int i = 0; i < evaluationConfig.getConfigThreads(); i++) {                // 初始化执行工作                PerformanceEvaluationTask task = new PerformanceEvaluationTask(evaluationConfig.getConfigWarmUp(),                        statement, statisticsCalculator);                Thread t = FACTORY.newThread(task);                taskList.add(task);                // 子线程执行工作                t.start();            }            //主线程沉睡期待            Thread.sleep(evaluationConfig.getConfigDuration());        } finally {            //具体详情,当执行打断时,被打断的工作可能曾经开始执行(尚未执行完),会呈现主线程往下走,被打断的线程也在持续走的状况            for(PerformanceEvaluationTask task : taskList) {                task.setContinue(false);    //终止执行的工作            }        }        // 更新统计信息        evaluationContext.setStatisticsCalculator(statisticsCalculator);        evaluationContext.runValidation();        generateReportor();    }    /**     * 报告生成     */    private synchronized void generateReportor() {        for(Reporter reporter : reporterSet) {            reporter.report(testClass, evaluationContextSet);        }    }}

这里是最外围的实现局部,主流程如下:

(1)依据配置,创立对应的工作子线程

(2)依据配置,初始化子工作,并且执行

(3)主线程进行沉睡期待

(4)主线程沉睡完结,打断子线程自行,更新统计信息

(5)依据统计信息,生成对应的测试报告文件

PerformanceEvaluationTask

子工作的实现也值得注意,外围实现如下:

public class PerformanceEvaluationTask implements Runnable {    /**     * 热身工夫     */    private long warmUpNs;    /**     * junit statement     */    private final Statement statement;    /**     * 统计计算者     */    private StatisticsCalculator statisticsCalculator;    /**     * 是否持续标记位     */    private volatile boolean isContinue;    public PerformanceEvaluationTask(long warmUpNs, Statement statement, StatisticsCalculator statisticsCalculator) {        this.warmUpNs = warmUpNs;        this.statement = statement;        this.statisticsCalculator = statisticsCalculator;        this.isContinue = true; //默认创立时继续执行    }    @Override    public void run() {        long startTimeNs = System.nanoTime();        long startMeasurements = startTimeNs + warmUpNs;        while (isContinue) {            evaluateStatement(startMeasurements);        }    }    /**     * 执行校验     * @param startMeasurements 开始工夫     */    private void evaluateStatement(long startMeasurements) {        //0. 如果继续执行为 false,退出执行。        if(!isContinue) {            return;        }        //1. 筹备阶段        if (nanoTime() < startMeasurements) {            try {                statement.evaluate();            } catch (Throwable throwable) {                // IGNORE            }        } else {            long startTimeNs = nanoTime();            try {                statement.evaluate();                statisticsCalculator.addLatencyMeasurement(getCostTimeNs(startTimeNs));                statisticsCalculator.incrementEvaluationCount();            } catch (InterruptedException e) { // NOSONAR                // IGNORE - no metrics            } catch (Throwable throwable) {                statisticsCalculator.incrementEvaluationCount();                statisticsCalculator.incrementErrorCount();                statisticsCalculator.addLatencyMeasurement(getCostTimeNs(startTimeNs));            }        }    }    /**     * 获取耗费的工夫(单位:毫秒)     * @param startTimeNs 开始工夫     * @return 耗费的工夫     */    private long getCostTimeNs(long startTimeNs) {        long currentTimeNs = System.nanoTime();        return currentTimeNs - startTimeNs;    }    //region getter & setter    public boolean isContinue() {        return isContinue;    }    public void setContinue(boolean aContinue) {        isContinue = aContinue;    }    //endregion}

这个工作,次要负责统计工作的耗时。

统计对应的胜利数量、异样数量等。

通过 volatile 定义的 isContinue 变量,便于在主线程沉睡完结后,终止循环。

ps: 这里还是能够发现一个问题,如果 statement.evaluate(); 曾经开始执行了,那么无奈被中断。这是一个能够改良的中央。

小结

本篇从 junit rules 讲起,剖析了整个性能测试工具的实现原理。

总的来说,实现思路并不是很难,所有简单的利用,都是有简略的局部组成

文中为了便于大家了解,对源码局部做了大量简化。

如果想获取残缺的源码,请返回开源地址:https://github.com/houbb/junitperf。

我是老马,期待与你的下次重逢。

当然,兴许你能够发现这种形式还是不够优雅,junit5 为咱们提供了更加弱小的性能,咱们下一节将解说 junit5 的实现形式。

参考资料

https://github.com/houbb/juni...

https://github.com/junit-team...