软件质量的重要性和必要性已被越来越多的软件开发人员所认识,各种质量保障工具和流程层出不穷。从根本上讲,软件是通过程序代码实现的,是代码质量的外在表现。只有提高代码质量,才能从根本上提高软件的质量。要保证并提高代码质量,可以从多个角度入手,譬如良好的设计、统一的编码规范、强化的代码评审、有效的代码静态分析等,除此之外,我们还应该从代码运行分析入手,发现软件的性能和内存瓶颈,保证代码的运行质量。
通常而言,除了功能实现以外,影响代码运行质量的原因可以归为三种类型:
- 内存问题,如在用户使用时程序死机或者突然崩溃;
- 代码覆盖率问题,如发现的问题没有被测试过;
- 性能问题,如与大量数据或者特定的数据相关的运行速度变慢的问题;
上述问题,必须在应用程序运行期间,借助于代码运行分析工具,通过收集和研究运行时数据,加以预防,以提高代码运行质量。
内存分析
运行时内存错误和泄漏是应用程序中最难检测的问题之一,也是最难分析和修复的,因为内存泄露源和错误的表现是分离很远的,很难将导致错误的原因和最终的效果联系起来。仅仅通过检查源代码是很难预测和理解这些错误发生的可能性和发生的情景的。此外,这些错误通常出现在特定条件下,很难复现。通常,这需要相当多的调试才能发现导致内存错误的程序逻辑和设计。
在测试中进行内存分析,将会增加组织的总体效率,应该使内存分析成为习惯。因为通过使用内存分析工具收集相关数据,可以大大减少开发人员花在调试和寻找问题所在的时间。
在下面的例子程序中,在第5行分配内存时,忽略了字符串终止符"\0"所占空间导致了第8行的数组越界写(Array
Bounds Write)和第9行的数组越界读(Array Bounds Read);在第7行,打印尚未赋值的str2将产生访问未初始化内存错误(Uninitialized
Memory Read);在第11行使用已经释放的变量将导致释放内存读和写错误(Freed Memory
Read and Freed Memory Write);最后由于str3和str2所指的是同一片内存,第12行又一次释放了已经被释放的空间
(Free Freed Memory)。
1
#include <iostream>
2
using namespace std;
3
int
main(){
4 char*
str1="four";
5 char*
str2=new
char[4]; //not
enough space
6 char*
str3=str2;
7 cout<<str2<<endl; //UMR
8 strcpy(str2,str1); //ABW
9 cout<<str2<<endl; //ABR
10
delete str2;
11
str2[0]+=2; //FMR
and
FMW
12
delete str3; //FFM
13
}
上面的代码虽然包含许多难以发现的内存错误,但可以编译连接,而且可以在很多平台上运行。如果这些内存错误不经排除,在特殊配置下触发,会造成不可预见的错误。
借助一些高级内存调试工具,可以快速准确定位导致这些内存泄露错误的原因,大大减少调试的时间和复杂性。Purify即是此类工具之一,它既可以检查不同的模块,确保它们能够正确地共同工作,又可以揭示出在单独开发期间不明显的代码的依赖关系。下图是一个采用Purify生成的C++应用程序的自动化测试报告。
对于C和C++程序来说,Purify的报告是类似MLK(Memory Leak,内存泄漏)、ABW(Array
Bounds Write,数组边界写)、ABR(Array Bounds Read,数组边界读取)等警告、错误和信息。每个这种Purify的缩写都表示一种应用程序中出现的内存错误,在此一并列出,由此可窥一斑:
代码覆盖率分析
即使已经为需求规格说明书中的每个功能点设计了测试用例,并在一个稳定的自动化环境中运行了这些用例,但用户仍然会报告出测试用例中没有发现的问题,因为总会有测试不到的代码路径。
代码覆盖率分析,可以针对测试用例的有效性给出有价值的反馈,扩展测试覆盖。还可以用来分析测试用例是否有冗余,如测试在代码的同一路径下反复运行,导致了不必要的时间延迟。另外,还可以帮助确认测试数据。例如,一个新引进的代码变更需要运行哪些自动化测试用例以进行回归确认。这时只需要检查覆盖率分析的数据,即可确定哪些自动化测试用例的子集需要运行,这样就可以在更短的时间内验证新的代码。
分析代码覆盖率,特别是当程序庞大时,不能完全依靠手工进行,必须借助专用的分析工具,才可以达到事倍功半的效果。通常,采用此类工具进行代码覆盖率分析的主要步骤为:
1. 运行由代码覆盖率分析工具处理过的应用程序
2. 收集覆盖率数据
3. 分析数据,找到哪些代码没有运行
4. 增加测试用例,尽可能地覆盖没有运行的代码
5. 重复上述步骤
目前,市场上有很多代码覆盖率分析工具,各有千秋,PureCoverage是笔者常用的工具之一。下图给出的例子是采用PureCoverage统计出的一个自动化测试程序的代码覆盖情况。
性能分析
性能分析是另外一种技术,是以收集程序运行时信息为手段研究程序行为的分析方法。性能分析的目的在于决定程序的哪个部分应该被优化,从而提高程序的速度或者内存使用效率。在新
Build 出现性能问题时,找出原因是至关重要的。通常,引起问题的原因基本是:
- 源代码的变更:如可能是不必要的模块影响了性能,也可能是算法的改变影响了性能。
- 测试代码的变更:如增加的用来改善代码覆盖率的测试用例可能导致变慢,也有可能这部分代码特别容易影响速度。
- 测试数据的变更:如大量的测试数据(或者不常见的数据)可能对代码产生压力,导致瓶颈。
- 环境的变更:如网络连接变慢。
我们应该使用一些能与测试协同工作的性能监控工具,设计专门的测试用例来测量性能,主动发现性能问题。通过性能监控工具,可以度量并分析在程序中所花费的时间,从而识别"热点"和无效的代码,找出应用程序的性能瓶颈;而且还可以立刻看到做出代码变更时所表现的不同性能,这样我们可以利用真实的代码而不是猜测来对程序的性能进行最优化操作。比如找出一个算法需要改良的位置后,把不同的优化方案放在虚拟机上运行,可以立刻分辨出找出孰优孰劣。通常,使用性能监控工具进行性能分析的步骤如下:
1. 设置基准,以对比特定数量自动化测试的期望值和实际值。
2. 使用处理过的程序运行自动化测试。
3. 收集数据。必须保证自动化测试在不崩溃的情况下,能够稳定运行足够合理的时间。
4. 分析收集到的数据,确定哪些测试或者方法看起来有问题。
5. 识别和研究这些问题,看看是否可以修正,然后重复整个过程。
Quantify是市场上流行的性能监控的工具之一,下图给出使用 Quantify 进行测试时自动生成的Callgraph。在Callgraph中,Quantify
收集有关哪些方法是调用者还是子调用者,以及这些调用花费了多长时间的信息,突出显示出最耗时间的代码路径。借助这些信息,可以清楚地找到代码中的“热点”,以确定是否有需要优化。
总结
在进行程序开发时,采用良好的设计和一致的编程规范是防止内存问题第一道也是最重要的措施。在此前提下,采用一些运行时分析软件可以很好地帮助开发人员发现忽略的内存问题,这应该成为软件自动化测试中的一个重要组成部分。
|