随着网络的发展,软件也越来越复杂,从独立的单机结构,到C/S结构、B/S结构、多层体系架构,面向服务的(SOA)结构等,集成的软件技术越来越多,支持的软件用户也越来越多。一种凸显在人们面前的问题是性能问题。很多软件系统在开发测试时没有任何问题,但是上线不久崩溃了,原因在于缺少了性能方面的验证。
  性能测试“从小做起”
  软件是否在上线之前进行性能测试能解决问题呢?不一定,如果性能测试进行得太晚,会带来修改上的风险。很多软件系统在设计的时候并没有很好地考虑性能问题和优化方案,等到整个软件系统开发出来后,测试人员忙着集成测试,开发人员也疲于应付发现的功能上的Bug,当所有功能上的问题都得到解决后,才想到要进行性能测试。性能测试结果表明系统存在严重的问题,如响应时间迟缓、内存占用过多、不能支持大量的数据请求,在大量用户并发访问的情况下会造成系统崩溃。如果此时再去修改程序已经非常困难了,因为要彻底地解决性能问题,需要重新调整系统的架构设计,大量的代码需要重构,这时的程序员已经筋疲力尽,不想再进行代码的调整了,因为调整带来的是大量的编码工作,同时可能引发大量的功能上的不稳定性和再次出现大量的Bug。
  这给测试人员一个启示,性能测试不应该只是一种后期的测试活动,更不应该是软件系统上线前才进行的“演练”,而应该是贯穿软件的生产全过程,如图所示。

  对于性能的考虑应该在架构设计时开始,对于架构原型要进行充分的评审和验证。由于架构设计是一个软件系统的基础平台,如果基础不好,也是根基不牢,性能问题会根深蒂固,后患无穷。性能测试应该在单元测试阶段开始。从代码的每一行效率,到一个方法的执行效率,再到一个逻辑实现的算法效率;从代码的效率,到存储过程的效率,都应该进行优化。单元阶段的性能测试可以考虑从以下几个方面进行:
  代码效率评估;
  应用单元性能测试工具
  数据库优化。
  应该注意每一行代码的效率,所谓“积少成多,水滴石穿”,一些看似细小的问题可以经过多次的执行累积成一个大的问题,也是一个量变到质变的过程。例如,在用C#编写代码的时候,有些程序员喜欢在一个循环体中使用string字符串变量,类似下面的代码:
  static void Loop1()
  {
  string digits = string.Empty;
  for(int i=0;i<100;i++)
  {
  //累加字符串
  digits+=i.ToString();
  }
  Console.WriteLine(digits);
  }
  这样一段代码其实是低效率的,因为string是不可变对象,字符串连接操作并不改变当前字符串,只是创建并返回新的字符串,因此速度慢,尤其是在多次循环中。应该采用StringBuilder对象来改善性能,例如下面的代码会快很多:
  static void Loop2()
  {
  //新建一个StringBuilder类
  Stringbuilder digits = new StringBuilder();
  for(int i=0;i<100;i++)
  {
  //通过StringBuilder类来累加字符串
  Digits.Append(i.ToString());
  }
  Console.WriteLine(digits.ToString());
  }
  类似的问题有很多,它们的特点是单个问题都很小,但是在一个庞大的系统中,经过多次的调用,问题会逐渐地被放大,直到爆发。这些问题都可以通过代码走查来发现。
  技巧:如果测试人员不熟悉代码怎么办呢?那么可以借助一些代码标准检查工具,例如FxCop、.TEST等,来帮助自动查找类似的问题。
  测试人员可以使用一些代码效率测试工具来帮助找出哪些代码或方法在执行时需要耗费比较长的时间,例如AQTime是一款可以计算出每行代码执行时间的工具。如图所示,可以看出每一个方法甚至每一行代码的执行时间是多少。这对开发人员在查找代码层的性能瓶颈时,也会有很大的帮助。


  使用AQTime查找低效率的代码行
 

  除了代码行效率测试工具外,近还出现了一些开源的单元级别的性能测试框架,可以像使用XUnit这一类的单元测试框架一样,但是不是用于测试单元代码的正确性,而是用于测试函数、方法的性能是否满足要求。例如NTime是这样的一个小工具。
  NTime可以并发地运行同一个方法多次,查看能否达到预期的性能指标。例如,下面的代码使用NTime框架启动两个线程,在1秒钟内并发地执行MyTest方法多次。
  [TimerHitCountTest(98,Threads = 2,Unit = TimePeriod.Second)]
  Public void MyTest()
  {
  //调用被测试的方法
  MethodToBeTest();
  }
  如果测试结果表明能执行超过98次,则认为“MethodToBeTest”方法的性能达标,否则将被视为不满足性能的要求。