共计 6724 个字符,预计需要花费 17 分钟才能阅读完成。
前言
上一节介绍了 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…