服务热线:13616026886

技术文档 欢迎使用技术文档,我们为你提供从新手到专业开发者的所有资源,你也可以通过它日益精进

位置:首页 > 技术文档 > JAVA > 新手入门 > 基础入门 > 查看文档

认识p-unit:一款开源的性能测试工具

 p-unit 是一款开放源码的性能测试框架,和 junit 不同,junit 关注的是测试案例的正确性,而 p-unit 不仅关注测试案例的正确性,还收集测试案例的性能参数,默认情况下,p-unit 收集测试案例的时间和内存消耗情况,可以产生文件,图片,和 pdf 格式的报表。此外,p-unit 还支持参数化测试,多线程测试以及不同 java 虚拟机性能之间的比较。

p-unit 简介

 或许我们已经习惯了使用 junit 来写单元测试来保证代码质量(我也一直这么做),但可能经常碰到这样的问题:

程序多线程下正确性如何?

如何测试程序的性能?

当有多个方案可以选择时,技术上如何比较不同方案的性能?

 对于问题 1,我们或许听天由命?或是凭借人工分析,或是根据用户反馈?很多软件单线程下的单元测试覆盖率相当高,从而保证了代码的健壮性。然而多线程测试时常被忽略,这并不代表多线程测试不重要,相反,修正一个用户报告的多线程 bug 往往比单线程的要高出很多,因为测试案例经常不是 100% 可重现的。这更要求程序员在开发阶段充分的重视。目前多线程单元测试力度不够的一个重要原因是没有一个像 junit 那样易用的测试工具,另外重复写测试案例往往不被程序员接受。

 对于问题 2,一个成熟的关心性能的产品往往有一个性能测试平台。这个测试平台应该关注的是测试业务逻辑本身,而无需关心如何运行测试案例。你是否为写这样的测试平台痛苦过?以及花费时间在产生一些直观的报表上面?

 对于问题 3,我们往往写一个原型来比较不同产品之间的性能,如何比较执行速度和内存消耗?或是选择最适合你的虚拟机?

 p-unit 就是这么一款开源的性能测试软件,它能帮助你很好的解决上述问题。p-unit 可以:

多线程支持:同一个测试案例可以单线程执行,也可以多线程执行,测试案例开发者只需写一套测试案例。

参数化测试案例:很多测试案例,需要测试同一功能在不同数量级上的性能表现。

不同虚拟机性能测试:只需指定虚拟机路径,即可测试同一个测试案例在不同虚拟机上的表现,报表上可以非常直观显示性能差别。

事件机制构架:punit 是基于事件机制构架的,如果用户想定制报表,只需实现事件响应器,并注册该响应器到 punit 核心即可。

多线程执行测试案例

 在了解如何多线程执行测试案例之前,我们先了解一下如何利用 p-unit 单线程执行测试案例。不同于 junit, p-unit 测试用例无需继承任何测试类或是实现接口,即可执行 test 开始的方法。尽管 junit 4 中加入了注释(annotation) 的特性,但测试方法前缀为 "test" 仍然是测试者们的首选。因此如果你的 junit 测试案例遵循的是 test 命名规则,那么 p-uni t可以兼容运行 junit 测试案例。

下面的代码清单 1 是一个最为普通的测试案例:


清单 1. 测试案例 1

比如我们比较类库 foo1 的方法 bar() 和类库 foo2 的方法 bar() 哪个更符合自己的应用程序,我们需要测试该函数在应用程序可能的数量级的范围内的表现。有经验的开发者知道经常碰到在小数量级 a 更好大数量级 b 更好的局面,因此全面的测试对于代码的性能理解非常重要,能帮助开发者做出正确的决定。p-unit 支持将参数传给测试方法,测试案例需要实现 p-unit 的 parameterizable 接口,该接口的主要方法是返回一组参数列表,这组列表的参数将会一一传给测试方法。


清单 7. p-unit 参数化测试案例

public class paramtestclass implements parameterizable {
public static void main(string[] args) {
  new punitsolorunner().run(paramtestclass.class);
}

public parameter[] parameters() {
  return new parameter[] { new parameterimpl(10), new parameterimpl(20) };
}
public void testa(parameterimpl param) {
  sampleutil.dosomething();
}

public void testb(parameterimpl param) {
  sampleutil.dosomething();
}

public void testc(parameterimpl param) {
  sampleutil.dosomething();
}

public void setupafterwatchers(parameter param) throws exception {
}
public void setupbeforewatchers(parameter param) throws exception {
}
public void teardownafterwatchers(parameter param) throws exception {
}
public void teardownbeforewatchers(parameter param) throws exception {
}
static class parameterimpl implements parameter {
  private int _count;
  parameterimpl(int count) {
   _count = count;
  }
  public int count() {
   return _count;
  }
 
  public string tostring() {
   return string.valueof(_count);
  }
}
}

上述代码的执行结果为:


清单 8. p-unit 参数化测试案例输出

[solo] started running samples.paramtestclass
samples.paramtestclass
testa(10) - [57936.0bytes,447.0ms]
testa(20) - [33128.0bytes,61.0ms]
testb(10) - [24832.0bytes,137.0ms]
testb(20) - [0.0bytes,63.0ms]
testc(10) - [83560.0bytes,468.0ms]
testc(20) - [16528.0bytes,47.0ms]
total: 6, failures:0 (green) 1450.0ms

 从上述结果看出,每个方法被执行了 2 次,每次传入不同的参数。多线程运行参数化测试程序?相信读者已经明白怎么去实现了,只需将 punitsolorunner 替换成 punitconcurrentrunner。

运行环境测试案例

 随着 java 开源,出现了更多的 java 运行环境,除了 sun 的参考实现外,bea、ibm 均有自己的 java 运行环境,更有如 apache harmony 的开源运行环境(尽管现在 apache harmony 尚不能称为 java 运行环境)。运行环境测试案例,为运行环境开发者以及选择运行环境,都能提供一定的帮助。比如说下面的例子就是测试 java.util.arraylistjava.util.vector 在两个不同运行环境的表现。测试案例写法和普通的测试案例完全一样,我们只需告诉 p-unit 不同的运行环境的 java 路径以及正确的 classpath,然后调用 runvms 函数即可:


清单 9. p-unit 运行环境测试案例

public static void main(string[] args) {
punitsolorunner runner = new punitsolorunner();
runner.addpuniteventlistener(new overviewreporter(new imagerender()));
runner.runvms(listtestclass.class, new vm[] { vmconfig.harmony, vmconfig.sun });
}
public class vmconfig {
private static string classpath = " -cp correct_classpath_including_all_jars_and_path";
private static string harmony_path = "harmony_path//bin//java" + classpath;
private static string sun_path = "sun_path//bin//java" + classpath;
public static vm harmony = new vm(harmony_path, "harmony");
public static vm sun = new vm(sun_path, "sun");
}
public class listtestclass {

    private static final int list_count = 100000;
    private static object element = new object();
private random indexgenerator = new random();;
 
    public void testinsertarraylist() {       
        arraylist arraylist = new arraylist(list_count);
        insertsequence(arraylist);
        insertrandom(arraylist);
    }
    public void testinsertvector() {
     vector vector = new vector(list_count);
     insertsequence(vector);
     insertrandom(vector);
    }
    public void insertsequence(list list) {
        for (int i = 0; i < list_count; ++i) {
            list.add(element);
        }
    }
    public void insertrandom(list list) {
        for (int i = 0; i < list_count; ++i) {
            list.add(indexgenerator .nextint(list_count),element);
        }
    }
}


上述代码的运行结果如下:


图 1. 运行环境测试案例结果
认识p-unit:一款开源的性能测试工具(图一) 认识p-unit:一款开源的性能测试工具(图二)

 从上图中可以很直观的看出,笔者使用的 harmony 版本在该测试案例中速度更快(左图),但内存消耗更多(右图)。下一节将讲述如何输出报表,但或许你已经注意到了,代码非常简单。

 从上面的实例中我们已经看到 p-unit 的输出结果的两种形式,控制台和报表图片。默认情况下,p-unit 将输出到控制台。p-unit 采用事件机制,在运行器的每个节点都会提供通知事件。所有的输出都是通过注册事件响应器来实现的。这也表明了结果输出和运行器完全隔离,用户也可以定制自己的报表。p-unit 有 4 种内建输出,分别为控制台、文件、图片报表以及 pdf 报表。上一节的例子中我们已经看到图片报表,其代码为:


清单 10. 添加 p-unit 总体图片报表

 runner.addpuniteventlistener(new overviewreporter(new imagerender()));

 p-unit 内建的报表有分三种不同的粒度:总体级别(overviewreporter),testsutie 级别(testsuitereporter),以及测试案例类级别(testclassreporter)。这三种级别都可以输出图片格式或是 pdf 格式,因此,总共有六种类型的输出。上述的代码就是输出总体级别的图片。由于事件监听器是互相独立的,因此你可以既选择输出图片又选择输出 pdf 文件,只需再添加事件监听器即可:


清单 11. 添加多个 p-unit 事件监听器

runner.addpuniteventlistener(new overviewreporter(new imagerender()));
runner.addpuniteventlistener(new overviewreporter(new pdfrender()));


总结

 至此,你是否基本理解了 p-unit 的概念呢?简单,易用,关注多线程,关注性能,这就是 p-unit。此外 p-unit 还有很多很好的小特性,如 alphabetical 接口来保证执行测试函数的先后顺序等。使用 p-unit,让你的代码更健壮!

扫描关注微信公众号