摘要
JUnitPerf 是一个 来度量代码的性能和执行效率的一个性能测试工具, 通过 编写用于 JUnitPerf 的单元测试代码可以使这一过程自动化。从另外一个角度来说它是 JUnit 的一个扩展插件 。
假如你对这种类型的自动化测试感兴趣的话可以参考我写的书《 Pragmatic Project Automation 》
您也可以花两天时间光顾一下站点《 Test-Driven Development with JUnit Workshop 》,这里提供了一些很好的 JUnit 的学习途径并且还有 Mike Clark 的一些讲义和实践指导手册帮助你更深入地了解测试驱动开发。
目录
· 简介
· 使用目的
· JUnitPerf 下载
· JUnitPerf 安装
· JUnitPerf 构建与测试
· 如何使用 JUnitPerf
· 编写有效的 JUnitPerf 测试
· 局限性
· 技术支持
· 捐助
· 培训与指导
· 许可信息
· 感谢
· 相关资源
JUnitPerf 是基于 JUnit 的一个度量性能和执行效率的一个自动化测试框架(工具)。
JUnitPerf 包含以下两个主要的类(扩展了 JUnit ):
· TimedTest
TimedTest 用来执行测试,返回执行该测试所使用的时间。
TimedTest 构造方法中需要指定一个最大可接受的执行时间。默认情况下,执行该方法时会等待被执行的测试执行完毕,如果实际所用的时间超过了指定的最大时间则标识测试失败。另外你也可以通过在构造方法指定当实际执行时间超过最大可接受时间时不继续执行该测试,并标识测试未通过。
· LoadTest
LoadTest 用来模仿多个并发用户多次迭代执行测试。
很明显, JUnitPerf 是对 JUnit 测试框架的一个扩展。这种方式的扩展允许动态地增加 JUnit 测试用例来进行性能测试,不会影响到先前的测试。这样您就可以快速简易地构造出性能测试套件。
性能测试套件可以自动地,独立于其它的 JUnit 测试用例执行。实际使用中,一般要尽量避免把 JUnit 测试用例和 JUnitPerf 测试用例组织在一起,这样才能更加独立地执行测试套件,并且也可按不同的顺序执行。持续时间较长的性能测试可能会延长测试的时间,从而导致你不愿意去执行所有的单元测试。因此,这需要你有计划地不时地去执行该测试,而不必影响到其他工作。
JUnitPerf 倾向于针对已经有明确的性能要求或者执行效率要求,并且要保证代码重构后依然保持这样的目标的测试。例如,您可以使用 JUnitPerf 测试来确保在同样的条件下不会由于改变算法而导致性能降低。您也可以使用它来确保重构一个资源池后不会导致在负载情况下的执行效率降低(这种保证是通过比较条件改变前后的执行时间和效率,只提供一个度量的依据)。
从投入产出的角度来看维护一个注重实效的测试是相当重要的。传统的性能度量工具和技术首先会去找出性能问题的潜在出处,而 JUnitPerf 则用来不断地自动测试并且检查需求和实际的结果。
以下是一个实际使用场景的例子:
你有一个功能良好的程序,并且通过了必要的 JUnit 测试套件的测试验证功能通过。从这个角度来说你已经达到了设计所想象的目标。
然后使用一个性能度量工具来分析程序的哪部分执行时间最长。基于设计知识,您已经具有很好的工具对程序做实际的评估。并且重构后的代码清晰简洁,接下来的工作就是调整一小部分代码。
接下来就可以写 JUnitPerf 测试用例了,为这部分代码指定可接受的性能和效率参数。如果不对代码做任何改动的情况下直接进行测试将不会通过,证明测试用例是正确的。接着对代码做一些小的调整。
每次调整后都重新编译和运行 JUnitPerf 测试。如果实际的性能到达了预期的指标,测试就算是通过了。如果实际的性能达不到预期的指标,就需要继续调整过程直到测试通过。如果将来代码再次重构了你也可以重新运行测试。如果测试未通过,而同时之前的性能标准也提高了,这时就需要回溯到原来并且继续重构直到测试通过。
JUnitPerf 下载
JUnitPerf 1.9 是当前最新的版本。包含以前所有版本的功能。
本版需要 Java 2 和 JUnit 3.5 或以上版本。
发行包包含一个 JAR 文件,源代码,示例代码, API 文档和本文档。
JUnitPerf 安装
Windows
在 Windows 上按以下步骤安装:
1. 解压 junitperf-<version>.zip 文件到一个目录中,在系统环境变量中增加%JUNITPERF_HOME% ,值为文件解压后的目录。
2. 把 JUnitPerf 加到 CLASSPATH 路径中:
set CLASSPATH=%CLASSPATH%;%JUNITPERF_HOME%\lib\junitperf-<version>.jar
Unix (bash)
在 UNIX 上按以下步骤安装:
1. 解压缩 junitperf-<version>.zip 到相应的目录下。例如:$JUNITPERF_HOME 。
2. 修改文件的权限:
chmod -R a+x $JUNITPERF_HOME
3. 把 JUnitPerf 加到 CLASSPATH 路径中:
export CLASSPATH=$CLASSPATH:$JUNITPERF_HOME/lib/junitperf-<version>.jar
在 $JUNITPERF_HOME/lib/junitperf-<version>.jar 文件中已经包含有编译好的类文件。
构建
$JUNITPERF_HOME/build.xml 文件是Ant 构建文件。
可以使用以下命令构建 JUnitPerf :
cd $JUNITPERF_HOME
ant jar
测试
JUnitPerf 安装包中包含了用于跟 JUnitPerf 结合使用的 JUnit 测试用例的实例。
可以输入以下命令验证 JUnitPerf 安装是否正常:
cd $JUNITPERF_HOME
ant test
最好的方式是使用 JUnitPerf 中附带的示例,这里包含了各种类型的测试。
$JUNITPERF_HOME/samples 目录包含了本文中所讲的所有示例代码.
TimedTest
TimedTest 构造方法有两个参数,一个是已存在的JUnit 测试用例,另一个是预期的最大的执行时间。
例如要针对 ExampleTestCase.testOneSecondResponse() 方法创建一个执行时间的测试并且等待该方法执行完毕,如果时间超过1 秒则视为未通过。
long maxElapsedTime = 1000;
Test testCase = new ExampleTestCase("testOneSecondResponse");
Test timedTest = new TimedTest(testCase, maxElapsedTime);
同样地,如果想要在执行过程如果超出预期时间立即结束本次测试可以在 TimedTest 构造函数中增加第三个参数,举例如下:
long maxElapsedTime = 1000;
Test testCase = new ExampleTestCase("testOneSecondResponse");
Test timedTest = new TimedTest(testCase, maxElapsedTime, false);
以下代码创建了一个执行时间的测试,用来测试被定义在单元测试 ExampleTestCase.testOneSecondResponse() 方法所代表的功能执行的时间。
执行效率测试举例
import com.clarkware.junitperf.*;
import junit.framework.Test;
public class ExampleTimedTest {
public static Test suite() {
long maxElapsedTime = 1000;
Test testCase = new ExampleTestCase("testOneSecondResponse");
Test timedTest = new TimedTest(testCase, maxElapsedTime);
return timedTest;
}
public static void main(String[] args) {
junit.textui.TestRunner.run(suite());
}
}
测试的粒度决定于 JUnit 的测试用例,并被 JUnitPerf 所使用,因此有一定的局限性。最终获得的执行时间为测试用例中 testXXX ()方法的执行时间,包括 setUp() , testXXX() , 和 tearDown() 方法的执行时间。执行测试套件的时间包含测试套件中所有测试示例的setUp() , testXXX() , 和 tearDown() 方法的执行时间。所以,预期的时间还应该依照set-up 和tear-down 的执行时间来制定(把这部分时间也考虑进去)。
LoadTest
LoadTest 用来仿效多个用户并发执行多次来进行测试。
LoadTest 最简单的构造函数只有两个参数,测试用例和用户数,默认情况下该测试只迭代一次。
例如,创建一个 10 用户并发执行一次 ExampleTestCase.testOneSecondResponse() 方法:
int users = 10;
Test testCase = new ExampleTestCase("testOneSecondResponse");
Test loadTest = new LoadTest(testCase, users);
负载测试过程也可以指定一个额外的计数器实例用来指定用户并发执行之间的延迟时间。 ConstantTimer 类构造函数包含一个常量参数,用来指定延迟时间,如果指定为0 则表示所有的用户同时开始。RandomTimer 类可以构造出随机的延迟时间。
例如:创建一个负载测试, 10 个并发用户各执行一次 ExampleTestCase.testOneSecondResponse() 方法,各个用户之间延迟1 秒钟执行。
int users = 10;
Timer timer = new ConstantTimer(1000);
Test testCase = new ExampleTestCase("testOneSecondResponse");
Test loadTest = new LoadTest(testCase, users, timer);
为了仿效并发用户以指定迭代次数执行测试, LoadTest 类构造函数包含了 RepeatedTest 参数。这样就可以为每个测试用例指定迭代次数了。
例如:创建一个负载测试, 10 个并发用户,每个用户迭代执行 ExampleTestCase.testOneSecondResponse() 方法20 次,每个并发用户之间延迟1 秒。
int users = 10;
int iterations = 20;
Timer timer = new ConstantTimer(1000);
Test testCase = new ExampleTestCase("testOneSecondResponse");
Test repeatedTest = new RepeatedTest(testCase, iterations);
Test loadTest = new LoadTest(repeatedTest, users, timer);
或者这样来写:
int users = 10;
int iterations = 20;
Timer timer = new ConstantTimer(1000);
Test testCase = new ExampleTestCase("testOneSecondResponse");
Test loadTest = new LoadTest(testCase, users, iterations, timer);
如果负载测试要求测试在 setUp ()方法中包含特殊的测试状态,那么就应该使用 TestFactory 类来确保每个并发用户线程使用一个本地线程测试实例。例如创建一个 10 用户并发的测试,每个用户运行 ExampleStatefulTest 类的一个本地线程,可这样来写:
int users = 10;
Test factory = new TestFactory(ExampleStatefulTest.class);
Test loadTest = new LoadTest(factory, users);
如果测试其中的某一个方法,可以这样:
int users = 10;
Test factory = new TestMethodFactory(ExampleStatefulTest.class, "testSomething");
Test loadTest = new LoadTest(factory, users);
以下的例子是测试单元测试 ExampleTestCase.testOneSecondResponse() 方法对应的功能的一个负载测试,用来测试该功能的执行效率。其中有10 个并发用户,无延迟,每个用户只运行一次。LoadTest 本身使用了TimedTest 来得到在负载情况下ExampleTestCase.testOneSecondResponse() 方法的实际运行能力。如果全部的执行时间超过了1.5 秒则视为不通过。10 个并发处理在1.5 秒通过才算通过。