性能测试的六个阶段
——性能测试的方法论之二
在软件开发生命周期中,必须进行性能测试的阶段包括:
- 单元测试
- 应用集成测试
- 应用集成负载测试
- 生产阶段测试
- 生产阶段负载测试
- 容量评估
当前的软件工程方法论把开发工作分解为叠代的过程。每次叠代都指定了一套必须实现的用例。典型的模式是,第一次的叠代过程实现了应用的框架并且确保提供了组件间的通信功能。随后的叠代在第一次叠代建立的框架上增加应用的功能。
因为叠代是由所实现的用例 (或部分用例) 定义的,所以每次叠代都对性能测试有相应的要求。用例定义了额外的测试步骤和SLA的变动范围,QA应该基于此SLA进行测试。因此,所有后文的性能测试讨论都应该被应用于各个叠代过程。区分每次叠代工作的控制因素就是用例。
单元测试
在把组件提交到集成阶段之前,每个开发人员必须对他们的组件进行性能单元测试。传统的单元测试仅仅重视功能而忽略性能。
在单元测试期间,性能单元测试意味着需要用以下的工具分析组件:
- Memory profiler
- Code profiler
- Coverage profiler
Memory profiler 在用例开始前和在用例结束后运行垃圾回收并且记录堆的快照。从这些数据中,我们能看到用例的内存影响及该用例在内存中留下的具体对象列表。开发人员需要检查那些对象以确认在用例执行完成后那些对象就应该保留在内存中。当用例完成后,如果对象被疏忽而留在堆中,那么这就是Java内存泄漏,并且我们这些称为游离对象,有时也可称为遗留对象引用。
下一个需要寻找的内存问题称为对象循环。在用例执行期间,所记录的细粒度的堆的采样信息,结合创建和删除的数量,说明了对象被创建和删除的次数。如果对象被迅速地创建并删除,那么它将给JVM带来非常大的压力。每个被创建及删除的对象仅能由垃圾搜集回收,并且对象循环显著地增加垃圾回收的频率。这种情况通常发生在一个循环或嵌套循环内部的对象创建的情况。
让我们看看以下的代码:
for( int i=0; i<object.size(); i++ ) {
for( int j=0; j<object2.size(); j++ ) {
int threshold = system.getThreshold();
if( object[i].getThing() - object2[j].getOtherThing() > threshold ) {
// Do something
}
}
}
在这个例子中,外部循环遍历object中的所有项目,并且每个项目都遍历object2的所有项目。如果object包含1000个项目并且object2也包含了1000个项目,那么被定义在循环内部的代码将被执行1000 *1000次,或1百万次。在这种代码下,阈限变量将每次在内部循环运行时(当该对象的引用超出作用域范围时对象将被销毁) 被分配和销毁。如果你在Memory profiler里看到这个代码,你将看到一百万个阈限实例被创建并被销毁。
代码应该用如下的方式重写来消除这种情况:
int threshold = system.getThreshold();
int threshold = system.getThreshold();
for( int j=0; j<object2.size(); j++ ) {
if( object[i].getThing() - object2[j].getOtherThing() > threshold ) {
// Do something
}
}
}
现在,对与一百万次循环,阈限变量只分配一次。阈限变量的影响从至关重要变为微不足道。
在基于Web应用中,我们经常看到的关于对象循环的场景是在在请求的上下文中创建对象。在单次使用情况下,这并不是问题,不过一但当用户的负载显著增加时,这个问题就很快变得很明显。你必须做出的决定是,这个对象是否需要基于每个请求创建,或者如果一旦它被创建,那么可被缓冲以被随后的请求重用。如果对这个问题的回答是后者,那么你可以消除该对象的循环使用问题。图1显示当对象循环发生时堆的视图。

图1: 对象循环可通过查看一个细粒度的堆采样数据而可视化地识别出来。红色圈定的堆的区域表明,此时内存中对象正被迅速地创建和释放,这预示了潜在的对象循环问题。
应用集成测试
在组件通过单元测试,被认为可以满足要求而加入到应用之后,下一步是将他们集成到一个单独的应用中。当每次叠代结束时就是集成阶段,主要是确定不同组件是否可以一起发挥作用来满足叠代用例。在功能集成测试完成以后而且应用满足功能方面的用例之后,下一步对整个集成进行性能测试。
这种测试不是负载测试,更准确地说是小规模虚拟用户。虚拟用户执行我们原先定义的功能:尽量通过均衡的和有代表性的服务请求模拟最终用户。该测试的用户负载是在一个性能测试计划中定义和记录的,该计划是由应用技术负责人和应用业务负责人联合决定的。该测试的目的并不是为了分析应用,而是识别应用的问题,譬如资源竞争,过多的游历对象,对象循环和不良的算法等,这些都是在首次多用户测试时所有应用都可能出现的问题。
另外,因为集成测试可以识别由负载产生的应用功能问题和明显的性能问题,所以这也是第一次检验用例是否可达到其SLA。如果应用在轻负载下无法满足其用例,那么进行负载测试就没有意义。
应用集成测试
现在应用已经完全集成了,达到它的所有功能需求并且能在低负载下满足其SLA,现在应该执行性能负载测试。该测试是一个在计划用户量下的满负载负载测试,该计划用户量是应用在上线后将最终支持用户数量。
这个测试应该分两个阶段执行:
1.在最小化的监测下执行。
2.在详细的监测下执行。
在第一阶段测试,目的是了解代码在真实的负载下是否达到它的SLA。当应用上线时,它将在最少的监测下运行。在第一阶段测试中,我们尽量让应用取得成功。
第二阶段测试,我们启动详细监测,或者对整个应用监测或者以分步骤方式(用过滤器只采集服务请求的一个子集),以便我们能识别性能瓶颈。甚至已经达到它们SLA的应用也可能有瓶颈。如果我们在这个阶段中识别并修改,那么在随后的叠代中他们就不会成为严重问题。
性能测试计划的这个阶段,对于应用性能调优是第一个好时机。这对于那种一直等待,直到应用完成才调优的传统方法来说,是巨大的改变。现在当应用的功能还是很简单时,我们就设法调优。如果应用建立在一个坚实基础上,那将会确保成功。
生产阶段测试
如果我们的应用可以始终在一个隔离的环境中运行,在该环境中我们可以充分使用应用服务器、操作系统和硬件资源,那么我们的性能调优和管理任务将会大大地简化。遗憾的是,为我们开发的每一个新应用增加硬件和软件许可都是很昂贵的,因此我们被迫在一个共享的环境中部署我们的应用。这意味着即使集成负载测试帮助我们调优应用,我们仍然需要一个可以真实模仿生产部署的测试环境。
这对于QA相当于是一项强加的任务。他们不仅需要为你的应用管理测试脚本,而且还包括在共享的环境中运行的所有应用。QA必须实现一种自动化的解决方案,可以产生可重复的和可测量的结果。
如同应用集成测试,这也不是一个负载测试,更恰当地说是一个识别各个应用所竞争资源的测试。在性能测试计划中,定义的负载是最小化。如果出现竞争的问题,那么需要深入的分析并识别问题。这也是需要自动化测试和增量测试的真正原因。当你的应用需要在这个测试台上测试时,该测试台在过去已经通过了该测试,所以问题可能是你的应用中的某个部分或你的应用中与其他应用相联系的某个部分引起的。不管怎样,在工作测试台和失败测试台之间能改变的只有你的应用,这也是为诊断问题提供一个好开端。
生产阶段测试
当你的应用终于看起来成功地集成在共享的环境中时,应该提高用户负载从而反映生产情况。如果你的应用可以通过这个测试并满足它的SLA,那么你可以对对现在的方向充满信心。如果在这个测试中它不满足它的SLA,那么你需要更深入的监测,过滤你的应用服务请求并且识别新的瓶颈问题。
值得注意的是:简单地把你的新应用部署到现有的已经调优的环境是不够的。你需要重新调整新环境来继续支持现有的应用和负载。这也许意味着需要调整共享资源,譬如堆,线程池,JDBC连接池等。
容量评估
终于进入到这个阶段,你已经掌握了一个非常有效的应用叠代方法。在性能测试最后阶段将评估你的应用的容量。在这个阶段,对整个环境进行负载测试,同时将应用的期望使用情况与观测到的该环境的生产行为相比较。换句话说,你开始进行环境负载测试,然后开始按环境负载测试的同等比例提高资源的使用。同时,也在对SLA进行测试。
继续缓慢地增加负载,直到系统资源饱和,吞吐量开始降低并且响应时间显著延长。在这个测试期间,你应记录每个用例超出它的SLA时的负载情况,并且对各个用例的响应时间给予密切的关注。知道每个用例性能降低的速度是很重要的,它将反馈到后面的容量计划中。
容量评估提供了你的应用(和环境)的全貌,以便你能评估新的架构考虑。此外,记录基于叠代及其关联的容量评估,可以帮助深入了解叠代中增加的应用代码并且可以测量你的开发团队的能力和发展程度。
(北京铸锐数码科技有限公司 www.InnovateDigital.com)
|