介绍
  如果你没有执行单元测试,你会同时失去第一时间改善软件质量和削减开发时间和成本的机会。这种牺牲当然是令人失望的。在此之前,开发人员一直没有一种可行的方法来执行单元测试。现在,JAVA开发人员能够使用ParaSoft的Jtest自动化JAVA单元测试,而C/C++开发人员能够利用ParaSoft新的C++Test工具自动化C/C++单元测试。
  本文阐述了执行单元测试的方法、优点和难点,然后描述了C++Test任何能够给C/C++开发人员带来巨大的好处。
  什么是单元测试?
  我们对单元测试的定义是测试应用中小的单元,如C/C++中的一个类。C/C++单元测试的目的是执行每个类中的每一个方法或函数,检测所有存在的功能性问题、错误和构造弱点。发现这些典型的问题通常涉及到三种测试方法:黑盒测试、白盒测试和回归测试。黑盒测试通过确定类的公共界面是否依照定义执行,检查类的功能性;执行这种类型的测试不需要有关实现细节的知识。白盒测试通过确定类在遇到非预期输入时执行是否正确,检查所有的类的方法和函数(包括保护和私有成员)的健壮性;执行这种类型的测试需要对类的实现细节的完整知识。回归测试检查是否类的修改会在原来正确的代码中引入新的错误。
  什么是单元测试的优点?
  单元测试被公认为软件开发过程中的一个关键步骤。单元测试能够简化错误检测,在减少开发时间和成本的同时提高软件质量。
  单元测试促进错误检测的第一个方面是使你更接近错误。图1和图2说明了这一点。


  图1:应用测试

  图1显示了一个包含许多对象的应用的测试模型。大椭圆表示应用,小椭圆表示对象,箭头表示用户输入,红星表示潜在的错误。
  为了发现错误,必须修改输入,对象间的相互作用将迫使某对象引发潜在的错误。这一点无疑是有一定难度的。想象一下你站在一张台球桌前,所有的球围成一个三角形放置在桌子中间,要求你一杆击球将中间的球打入指定的袋中。这是一件多么困难的事情啊!像要设计一个输入从而发现应用中的一个错误一样。由于其难度,开发人员只能依赖应用软件的运行失败来发现错误,但却仍然没有测试到许多类。因此只做集成测试是一件非常困难的事情。
  单元级测试提供了一种更有效的发现错误的方法。如图2所示。


  图2:单元测试

  单独测试一个类时(与其它对象分离),由于更接近错误,找到潜在的错误会变得容易得多。用上述台球的例子来比喻,像是台面上只有壹两个球时,一杆将一个球击入指定的袋中。
  单元测试促进错误检测的第二个方面是,将你从在问题中艰难跋涉去修正一个简单错误的困境中解救出来。因为错误是相互关联和作用的,在较高的层次上找到和修正一个错误,常常会发现另外的问题。当你在较高层次上测试时,原始的错误象是洋葱的内层一样,另外的错误象紧包着内层的其它层:只有把外层都剥掉才能看到内层。当你测试每一个类时,错误还很少有机会建立在其它错误之上,并相互作用引起奇怪的行为。因此在单元级上检测错误会容易的多。
  重要的结果是,更容易的错误检测能够在改善应用质量的同时大量削减开发时间和成本。首先,由于能够更容易地找到错误,会减少发现它们的时间和资源。其次,由于你一写完一个类,能发现和改正其中的错误,你不需要在以后花费时间重新了解和摸索。后,重要的理由是:由于类的相互作用和关联性,在单元级修改一个类只会影响到原始的类,而在较高的层次上修改一个类可能会改变多个程序部件的设计和功能性。越迟发现问题,通常要修改越多的代码。当修改的代码量增加时,两个其它因素也会随之增加:
  修改每一个错误所需的时间和费用。
  在代码中引入新的错误的机会。
  一次又一次的研究证明,随着问题被检测出来的时间的推迟,发现软件错误所需的时间和成本会惊人地增加。请看下面的研究结果:
  IBM:根据IBM的一份内部资料指出,确定软件错误的相对成本是:在设计阶段,1.5;编码前,1;编码中,1.5;测试前,10;测试中,60;交付后,100。[Watts Humphrey]
  TRW:确定错误的相对时间:需求分析阶段,1;设计阶段,3-6;编码阶段,10;开发测试阶段,15-40;接受性测试阶段,30-70;应用运行中,40-1000。[Boehm]
  IBM:确定错误的相对时间:设计评审,1;代码检查,20;测试,82。[Remus]
  JPL:Bush得出的每个错误的平均成本:编码,US$90-US$120;测试,US$10,000。[Bush]
  Freedman and Weinberg:使用设计评审和代码检查手段的项目在测试时发现错误的数量会减少10倍,测试成本降低50%-8%,包括评审和检查的成本。[Freedman]
  什么是单元测试的难点?
  基于上述信息,单元测试看上去象一剂药。如果是这样的话,为什么每一个C/C++开发人员不马上对每一个类进行单元测试?目前可以使用的技术来说,对C/C++的单元测试是一件困难、烦琐和耗时的事情,没有很好的工具来自动化这一过程,使得许多C/C++开发人员望而生畏。
  执行单元测试的第一步是是目标类变得可测。这需要两个工作:
  设计一个运行目标类的测试驱动程序。
  设计桩函数,它们为被测类所引用的任何外部资源返回值。
  建立一个测试驱动,需要建立一个新的类,除了测试原始类以外它不能用于任何其它目的。测试驱动应该具有下列特性:
  一个指定设置和清除的标准方式。
  一个选择个别测试和所有有效测试的方法。
  一个分析输出的预期(或非预期)结果的机制。
  一个标准的错误报告形式。
  为了充分而正确地测试类,你需要设计一个能够完全检查被测类的测试驱动;若干次修改和重写这样一个测试驱动是免不了的。一旦建立了测试驱动,你必须仔细检查它不能包含任何错误。测试驱动中的一个错误会破坏这个测试,但是你无法单独测试一个类,你也不能测试测试驱动本身。
  如果你的类引用任何还没有准备好或不可访问的外部资源(如外部文件、数据库和CORBA对象等),你必须建立相应的桩函数,它们的返回值类似于这些实际的外部资源应该返回的值。当建立这些桩函数时,你需要选择桩函数的返回值,它们将影响程序的执行路径...
  为了测试类的功能性必须执行任何的路径,足够的路径能够提供彻底的测试覆盖性。