前言

上一节介绍了 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/junitperf

https://github.com/junit-team/junit4/wiki/Rules

关于 junit4 90% 的人都不知道的特性,详解 junitperf 的实现原理的更多相关文章

  1. 90%的人都不知道的Node.js 依赖关系管理(上)

    转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 原文参考:https://dzone.com/articles/nodejs-dependency-mana ...

  2. 90%的人都不知道的Node.js 依赖关系管理(下)

    转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 原文参考:https://dzone.com/articles/node-dependency-manage ...

  3. 关于setTimeout()你所不知道的地方,详解setTimeout()

    关于setTimeout()你所不知道的地方,详解setTimeout() 前言:看了这篇文章,1.注意setTimeout引用的是全部变量还是局部变量了,当直接调用外部函数方法时,实际上函数内部的变 ...

  4. 90%的开发者都不知道的UI本质原理和优化方式

    前言 很多开发者在工作中一直和UI打交道,所以认为UI非常的简单! 事实上对于90%的开发者来说,不知道UI的本质原理. 虽然在开发中,我们在接到产品的UI需求之后,可以走捷径照抄大型APP代码,但是 ...

  5. 99% 的人都不知道的 Kubernetes 网络疑难杂症排查方法

    原文链接:Kubernetes 网络疑难杂症排查分享 大家好,我是 roc,来自腾讯云容器服务 (TKE) 团队,经常帮助用户解决各种 K8S 的疑难杂症,积累了比较丰富的经验,本文分享几个比较复杂的 ...

  6. 很多人都不知道的监听微信、支付宝等移动app及浏览器的返回、后退、上一页按钮的事件方法

    版权声明:本文为博主原创文章,未经博主允许不得转载. 在实际的应用中,我们常常需要实现在移动app和浏览器中点击返回.后退.上一页等按钮实现自己的关闭页面.调整到指定页面或执行一些其它操作的 需求,那 ...

  7. 90% 前端开发者都不知道的 JavaScript 实用小技巧

    面试神器之数组去重 const a = [...new Set([1, 2, 3, 3])] >> [1, 2, 3] 操作数组担心 falsy 值? const res = myArra ...

  8. 大部分人都不知道的8个python神操作

    01 print 打印带有颜色的信息 大家知道 Python 中的信息打印函数 Print,一般我们会使用它打印一些东西,作为一个简单调试. 但是你知道么,这个 Print 打印出来的字体颜色是可以设 ...

  9. 震惊!90%的程序员不知道的Java知识!

    震惊!90%的程序员不知道的Java知识! 初学Java的时候都会接触的代码 public static void main(String[] args){ ... } 当时就像背公式一样把这行代码给 ...

随机推荐

  1. 模型压缩95%:Lite Transformer,MIT韩松等人

    模型压缩95%:Lite Transformer,MIT韩松等人 Lite Transformer with Long-Short Range Attention Zhanghao Wu, Zhiji ...

  2. 自主数据类型:在TVM中启用自定义数据类型探索

    自主数据类型:在TVM中启用自定义数据类型探索 介绍 在设计加速器时,一个重要的决定是如何在硬件中近似地表示实数.这个问题有一个长期的行业标准解决方案:IEEE 754浮点标准.1.然而,当试图通过构 ...

  3. 痞子衡嵌入式:嵌入式里串口(UART)自动波特率识别程序设计与实现

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家分享的是嵌入式里串口(UART)自动波特率识别程序设计与实现. 串口(UART)是嵌入式里最基础最常用也最简单的一种通讯(数据传输)方式,可以说 ...

  4. windows10环境下gcc环境变量的配置

    1.首先打开控制面板-系统和安全-系统-高级系统设置,打开环境变量 2.在用户变量里找到Path,点击编辑,点击新建,找到Qt的tools安装目录并将目录复制进去保存,我的目录是C:\Qt\Qt5.9 ...

  5. InnoDB 静态数据加密的常见问题合集

    1. 数据是否为有权查看数据的用户解密? 是的.InnoDB静态数据加密旨在透明地在数据库中应用加密,而不会影响现有应用程序.以加密格式返回数据会破坏大多数现有应用程序. InnoDB静态数据加密提供 ...

  6. 配置IPv6公网地址DDNS并开放外网访问端口

    目前使用三大运营商宽带服务都会下发公网IPv6地址,这样我们想要在外网访问家里的路由.NAS等设备就可以直接通过IPv6地址来访问了.但是每次重新拨号后IPv6地址都会改变,而且IPv6的地址很长,这 ...

  7. 云原生时代的Java

    原文链接(作者:周志明):https://time.geekbang.org/column/article/321185 公开课链接:https://time.geekbang.org/opencou ...

  8. 身为一枚优秀的程序员必备的基于Redis的分布式锁和Redlock算法

    1 前言 今天开始来和大家一起学习一下Redis实际应用篇,会写几个Redis的常见应用. 在我看来Redis最为典型的应用就是作为分布式缓存系统,其他的一些应用本质上并不是杀手锏功能,是基于Redi ...

  9. 用transform和rem哪个好

    个人觉得电脑端的用transform好,毕竟电脑端的项目基本都会固定屏幕比列,16:9.28:9.32:9的 一个固定的设计稿就能很好的适配. 移动端用rem比较好,移动端的屏幕比列太杂,使用rem自 ...

  10. 腾讯云TKE-基于 Cilium 统一混合云容器网络(下)

    前言 在 腾讯云TKE - 基于 Cilium 统一混合云容器网络(上) 中,我们介绍 TKE 混合云的跨平面网络互通方案和 TKE 混合云 Overlay 网络方案.公有云 TKE 集群添加第三方 ...