软件测试系列之单元测试
 

2009-05-26 作者:Delores 来源:Delores的blog

 

1 基本理论

整理资料时发现以前给兄弟们灌输的单元测试的一些基本知识,放在这里供大家参考。里面参考了网上很多朋友的资料,这里没一一列出,一并谢过。ppt转的,比较乱,ppt已经上传到个人资源的,需要的可以去下载看看。不知道怎么搞的,ppt转成word格式是正确的,word里粘贴到这里,在编辑方式下也是对的,可是浏览文章时就乱七八糟了。只有在记事本里中转了一下,丧失了很多排版上的信息。

现象

  • 投入太多的精力,找 bug,而新的代码仍然会出现类似 bug;
  • 写完代码,心里没底,是否有大量 bug 等待自己;
  • 新修改的代码不知道是否影响其他部分代码;
  • 由于牵扯太多,导致不敢进行修改代码;
    ...

一、软件测试基本理论

  • 目的:对软件测试有个整体认识
  • 软件测试
  • 软件测试分类
  • 软件开发全过程检测及测试自动化
  • V模型与X模型
  • TDD( Test-Driven Development)

什么是软件测试?

在软件投入运行前,对软件需求分析、设计规格说明和编码的最终复审,是软件质量保证的关键步骤。

软件测试的概念:

软件测试是为了发现错误而执行程序的过程。

或者说,软件测试是根据软件开发各个阶段的规格说明和程序的内部结构而精心设计一批测试用例(即输入数据及其预期结果),并利用这些测试用例去执行程序,以发现程序错误的过程。

测试的目的

  • 测试是程序的执行过程,目的在于发现错误;
  • 一个好的测试用例在于能发现至今未发现的错误;
  • 一个成功的测试是发现了至今未发现的错误的测试
  • 也可以这样说,测试的目标是以较少的用例、时间和人力找出软件中潜在的各种错误和缺陷,以确保系统的质量
  • 一个被人忽略的软件测试目的是:测试可以帮助发现当前开发工作所采用的软件过程(也是一个“软件”)的缺陷,以便进行改进。
  • 首先,测试并不仅仅是为了要找出错误。分析错误产生的原因和错误在开发的哪一个阶段产生,具有非常重要的意义。
  • 通过分析错误产生于哪一个开发阶段、而又在哪一个阶段被发现,我们可以判断从错误的产生到错误的发现,跨越了多少个开发阶段。
  • 软件开发的一条重要原则是尽早发现与修正错误。
  • 正确分析与利用测试的结果,我们可以非常有效地进行软件过程改进

软件测试原则 2-1

  • 完全测试程序是不可能的
    -输入量太大
    -输出结果太多
    -软件实现途径太多
    -软件说明书没有客观标准。从不同角度看,软件缺陷的标准不同。

软件测试原则 2-2

  • 软件测试是有风险的行为
  • 测试无法显示潜伏的软件缺陷
  • 找到的软件缺陷越多,就说明软件缺陷越多
  • 并非所有软件缺陷都能修复
  • 软件测试一项讲究条理的技术专业

软件测试分类

从是否需要执行被测软件的角度,可分为:

-静态测试
-动态测试

从测试是否针对系统的内部结构和具体实现算法的角度来看,可分为 :

-白盒测试
-黑盒测试

软件测试方法-静态和动态

  • 静态检查
    确保系统按照组织的标准和过程运行,主要依赖于评审和非运行的手段来检查。通常包括需求评审、设计评审、代码走查和代码检查。
  • 动态检查
    在生命周期中进行测试(运行)。通常包括单元测试、集成测试、系统测试、用户的验收测试。

静态测试

  • 审查 (Inspection)
    -软件的一种基本测试方法,它以一系列典型问题为依据进行检测。
  • 走查 (Walkthrough)
    -一对一的审查,比审查更加仔细。
  • 回顾(Review)
    -以发现软件中存在的错误和缺陷为目的的一种软件测试方法,它是在软件证实执行之前完成。

静态和动态测试进行结构和功能测试

测试技术

黑盒测试

黑盒测试也称功能测试或数据驱动测试,它是在已知产品所应具有的功能,通过测试来检测每个功能是否都能正常使用,在测试时,把程序看作一个不能打开的黑盆子,在完全不考虑程序内部结构和内部特性的情况下,测试者在程序接口进行测试,它只检查程序功能是否按照需求规格说明书的规定正常使用,程序是否能适当地接收输入数锯而产生正确的输出信息,并且保持外部信息(如数据库或文件)的完整性。

“黑盒”测试着眼于程序外部结构、不考虑内部逻辑结构、针对软件界面和软件功能进行测试。“黑盒”法是穷举输入测试,只有把所有可能的输入都作为测试情况使用,才能以这种方法查出程序中所有的错误。实际上测试情况有无穷多个,人们不仅要测试所有合法的输入,而且还要对那些不合法但是可能的输入进行测试。

它不仅应用于开发阶段的测试,更重要的是在产品测试阶段及维护阶段必不可少。主要用于软件确认测试。

黑盒测试方法主要有:

  • 等价类划分
  • 边值分析
  • 因果图
  • 错误推测

白盒测试

白盒测试也称结构测试或逻辑驱动测试,它是知道产品内部工作过程,可通过测试来检测产品内部动作是否按照规格说明书的规定正常进行,按照程序内部的结构测试程序,检验程序中的每条通路是否都有能按预定要求正确工作,而不顾它的功能。

使用被测单元内部如何工作的信息,允许测试人员对程序内部逻辑结构及有关信息来设计和选择测试用例,对程序的逻辑路径进行测试。基于一个应用代码的内部逻辑知识,测试是基于覆盖全部代码、分支、路径、条件。

白盒测试的主要方法

  • 逻辑驱动测试
  • 基本路径测试

主要用于软件验证。

使用程序设计的控制结构导出测试用例。

逻辑驱动测试:

主要是测试覆盖率,以程序内在逻辑结构为基础的测试。包括以下6种类型:

  • 语句覆盖
  • 判断覆盖
  • 条件覆盖
  • 判定-条件覆盖
  • 条件组合覆盖
  • 路径测试

白盒测试的主要目的

  • 保证一个模块中的所有独立路径至少被执行一次;
  • 对所有的逻辑值均需要测试真、假两个分支;
  • 在上下边界及可操作范围内运行所有循环;
  • 检查内部数据结构以确保其有效性。

概念

  • 语句覆盖:语句覆盖就是设计若干个测试用例,运行被测试程序,使得每一条可执行语句至少执行一次;
  • 判定覆盖(也称为分支覆盖):设计若干个测试用例,运行所测程序,使程序中每个判断的取真分支和取假分支至少执行一次;
  • 条件覆盖:设计足够多的测试用例,运行所测程序,使程序中每个判断的每个条件的每个可能取值至少执行一次;
  • 判定-条件覆盖:设计足够多的测试用例,运行所测程序,使程序中每个判断的每个条件的所有可能取值至少执行一次,并且每个可能的判断结果也至少执行一次,换句话说,即是要求各个判断的所有可能的条件取值组合至少执行一次;
  • 条件组合测试:设计足够多的测试用例,运行所测程序,使程序中每个判断的所有可能的条件取值组合至少执行一次;
  • 路径测试:设计足够多的测试用例,运行所测程序,要覆盖程序中所有可能的路径。

软件开发全过程检测及测试自动化

  • 单元测试(unit test )
    由编程的开发人员自行计划与完成的,针对单个或相关联的一组程序单元的测试。
  • 组装测试(inegration test )
    计划于设计阶段,由开发人员与测试人员合作完成的,针对结合起来的不同单元以及它们的接口的测试。
  • 系统测试(system test ):(可认为包括“可用性与图形用户界面测试”)
    测试整个系统,以证实它满足要求所规定的功能、质量和性能等方面的特性。
  • 回归测试(regression test ):
    用于验证改变了的系统或其组件仍然保持应有的特性。
  • 验收测试(acceptance test ):
    测试整个系统,以保证其达到可以交付使用的状态。

V模型

V模型

  • 单元测试、集成测试、系统测试、验收测试。是“从小到大”、“由内至外”、“循序渐进”的测试过程,体现了“分而治之”的思想。
  • 单元测试的粒度最小,一般由开发小组采用白盒方式来测试,主要测试单元是否符合“设计”。
  • 集成测试界于单元测试和系统测试之间,起到“桥梁作用”,一般由开发小组采用白盒加黑盒的方式来测试,既要验证“设计”又要验证“需求”。

V模型

  • 系统测试的粒度最大,一般由独立测试小组采用黑盒方式来测试,主要测试系统是否符合“需求规格说明书”。
  • 验收测试与系统测试非常相似,主要区别是测试人员不同,验收测试由用户执行。

测试内容

  • 测试内容一般包含
  • 接口与路径测试。
  • 功能测试、健壮性测试、性能测试、用户界面测试、安全性测试、压力测试、可靠性测试、安装/反安装测试…

测试阶段对应表

接口与路径测试 3-1

  • 接口测试:数据一般通过接口输入和输出,接口测试一般是白盒测试的第一步。
    ― 输入参数有“典型值”、“边界值”、“异常值”
    ― 输出包括函数的返回值和输出参数。
    ― 实际输出与期望的输出不一致,那么说明程序有错误。
  • 一个函数体内的语句可能只有十几条,但逻辑路径可能有成千上万条。所以应该根据经验选择关键的路径测试。

接口与路径测试 3-2

  • 路径测试的检查表
  • -数据类型、变量值、逻辑判断、循环、内存管理、文件I/O、错误处理
  • 预防一些重要的路径没有被测试的措施有:
    ― 观察是否有程序语句从来没有被执行过。
    ― 要特别留意函数体内的错误处理程序块。

接口与路径测试 3-3

  • 接口与路径测试用例的参考模板

功能测试 3-1

  • 功能测试的基本方法是构造一些合理输入(在需求范围之内),检查输出是否与期望的相同。如果两者不一致,即表明功能有误。
  • 难点在于如何构造有效的输入。

功能测试 3-2

  • 功能测试的测试方法:等价划分法和边界值分析法。
    ― 等价划分是指把输入空间划分为几个“等价区间”,在每个“等价区间”中只需要测试一个典型值就可以了。等价划分法来源于人们的直觉与经验,可令测试事半功倍。
    ― “缺陷遗漏在角落里,聚集在边界上”。边界值测试法是对等价划分法的补充。如果A和B是输入空间的边界值,那么除了典型值外还要用A和B作为测试用例。

功能测试 3-3

  • 功能测试用例的参考模板

性能测试 3-1

  • 性能测试即测试软件处理事务的速度,一是为了检验性能是否符合需求,二是为了得到某些性能数据供人们参考。
  • 绝对值考虑:如数据送输速率是每秒多少比特。 “相对值”考虑:如某个软件比另一个软件快多少倍。
  • 性能测试中考虑运行环境的影响:例如网络环境、计算机主频,总线结构和外部设备都可能影响软件的运行速度。

性能测试 3-2

  • 性能测试的一些注意事项:
    ― 应当编写一段程序用于计算时间以及相关数据。
    ― 应当测试软件在标准配置和最低配置下的性能。
    ― 应当关闭那些消耗内存、占用CPU的其它应用软件(如杀毒软件)。
    ― 应当分档记录。例如传输文件的容量从100K到1M可以分成若干等级。
    ― 同一种输入情况在不同的时间可能得到不同的性能数据,可以取其平均值。

性能测试 3-3

  • 性能测试用例的参考模板

压力测试 2-1

  • 压力测试也叫负荷测试,即获取系统能正常运行的极限状态。
  • 压力测试的主要任务是:构造正确的输入,使劲折腾系统却让它刚好不瘫痪。
  • 压力测试的一个变种是敏感测试。在某种情况下,微小的输入变动会导致系统的表现(如性能)发生急剧的变化。

压力测试 2-2

  • 压力测试用例的参考模板

其他测试内容

  • 健壮性测试
  • 用户界面测试
  • 信息安全测试
  • 可靠性测试
  • 安装和反安装测试

测试常见问题 2-1

  • 问题1:有了“黑盒”测试为什么还要“白盒”测试?
  • 问题2:由于单元测试要写测试驱动程序,非常麻烦,能否等到整个系统全部开发完后,再集中精力进行一次性地单元测试呢?
  • 问题3:如果每个单元都通过了测试,把它们集成一起难道会有什么不妥吗?集成测试是否多此一举?

测试常见问题 2-2

  • 问题4:在集成测试的时候,已经对一些子系统进行了功能测试、性能测试等等,那么在系统测试时能否跳过相同内容的测试?
  • 问题5:既然系统测试与验收测试的内容几乎是相同的,为什么还要验收测试?
  • 问题6:能否将系统测试和验收测试“合二为一”?

总结 2-1

  • 测试可以将测试描述为一个运行程序以发现错误的过程。
  • 软件测试的准则:不完全测试、风险测试、无法显示潜伏错误、发现错误成线性增长、缺陷不能完全修复、测试有条理规程
  • 测试的方法:黑盒/白盒、静态/动态
  • 软件测试的各个阶段:单元测试、集成测试、系统测试、验收测试

总结 2-2

  • 测试的内容包括:接口/路径测试、功能测试、性能测试、压力测试、可靠性测试、安全性测试、用户界面测试、安装/反安装测试

X模型

测试驱动开发

  • TDD, Test-Driven Development
  • 测试驱动开发以测试作为开发过程的中心,它要求在编写任何代码之前,首先编写用于定义产品代码行为的测试,而编写的产品代码又以使测试通过为目标。测试驱动开发要求测试可以完全自动地运行,在代码进行重构前必须运行测试。

TDD基本做法

  • 1. 写一个测试程序。
  • 2. 让程序编译通过。
  • 3. 运行测试程序,发现不能运行。
  • 4. 让测试程序可以运行。
  • 5. 消除重复设计,优化设计结构。

测试产品说明书

  • 对于产品说明书的制定是个很重要的设计阶段,产品说明书的质量会直接影响到整个产品开发。
  • 测试产品说明书属于静态黑盒子测试。

常用测试用语-测试用例

  • 测试用例:编写用于输入输入的实际数制和预期结果。测试用例还明确指出使用具体测试用例产生的测试程序的任何限制 。
  • 使用目的:
    ― 测试用例应该设计为能够快速容易地发现尽可能多的错误。
    ― 应该通过使用和产生正确和错误的输入和输出来“检验”程序。
    ― 其目标是要使用合理范围内的条件,尽可能全面地测试所有模块乃至整个系统。

测试与调试-什么是缺陷

  • 缺陷:最终产品同用户的期望不一致
  • 缺陷的分类
  • 错误
  • 遗漏
  • 超出需求的部分
  • 缺陷(未触发)VS.错误(应首先解决)

测试与调试-调试的准则

  • 调试的方法:归纳法、演绎法和回溯法。
  • 常用调试技术使用诊断输出语句 (diagnostic output statement)、快照转储 (snapshot dump) 以及跟踪指令的断点 (instruction-dependent breakpoint)。

二、单元测试基本理论

  • 什么是单元测试(Unit Test)
  • 什么时候测试?
  • 为什么要进行单元测试
  • C/C++单元测试问答(摘要)

单元测试(Unit Test)

  • 工厂在组装一台电视机之前,会对每个元件都进行测试,这,就是单元测试。
  • 临时单元测试:代码覆盖率要超过70%都很困难
  • 充分的单元测试:提高软件质量,降低开发成本的必由之路。
  • 单元测试是在软件开发过程中要进行的最低级别的测试活动,在单元测试活动中,软件的独立单元将在与程序的其他部分相隔离的情况下进行测试。

单元测试(Unit Test)

  • 对于程序员来说,如果养成了对自己写的代码进行单元测试的习惯,不但可以写出高质量的代码,而且还能提高编程水平。
  • 在一种传统的结构化编程语言中,比如C,要进行测试的单元一般是函数或子过程。在象C++这样的面向对象的语言中,要进行测试的基本单元是类。对Ada语言来说,开发人员可以选择是在独立的过程和函数,还是在Ada包的级别上进行单元测试。单元测试的原则同样被扩展到第四代语言(4GL)的开发中,在这里基本单元被典型地划分为一个菜单或显示界面。

单元测试(Unit Test)

  • 单元测试不仅仅是作为无错编码一种辅助手段在一次性的开发过程中使用,单元测试必须是可重复的,无论是在软件修改,或是移植到新的运行环境的过程中。因此,所有的测试都必须在整个软件系统的生命周期中进行维护。

三、为什么要 进行单元测试

  • 一些流行的误解--反调论证
  • 其他好处
  • 单元测试的重要性

反证1:单元测试浪费了太多的时间

一旦编码完成,开发人员总是会迫切希望进行软件的集成工作,这样他们就能够看到实际的系统开始启动工作了。 这在外表上看来是一项明显的进步,而象单元测试这样的活动也许会被看作是通往这个阶段点的道路上的障碍, 推迟了对整个系统进行联调这种真正有意思的工作启动的时间。

反证1:单元测试浪费了太多的时间

 在这种开发步骤中,真实意义上的进步被外表上的进步取代了。系统能够正常工作的可能性是很小的,更多的情况是充满了各式各样的Bug。在实践中,这样一种开发步骤常常会导致这样的结果:软件甚至无法运行。更进一步的结果是大量的时间将被花费在跟踪那些包含在独立单元里的简单的Bug上面,在个别情况下,这些Bug也许是琐碎和微不足道的,但是总的来说,他们会导致在软件集成为一个系统时增加额外的工期, 而且当这个系统投入使用时也无法确保它能够可靠运行。

反证1:单元测试浪费了太多的时间

  • 在实践工作中,进行了完整计划的单元测试和编写实际的代码所花费的精力大致上是相同的。一旦完成了这些单元测试工作,很多Bug将被纠正,在确信他们手头拥有稳定可靠的部件的情况下,开发人员能够进行更高效的系统集成工作。这才是真实意义上的进步,所以说完整计划下的单元测试是对时间的更高效的利用。而调试人员的不受控和散漫的工作方式只会花费更多的时间而取得很少的好处。
  • 使用AdaTEST和Cantata这样的支持工具可以使单元测试更加简单和有效。但这不是必须的,单元测试即使是在没有工具支持的情况下也是一项非常有意义的活动。

反证2:仅仅证明代码做了什么

这是那些没有首先为每个单元编写一个详细的规格说明而直接跳到编码阶段的开发人员提出的一条普遍的抱怨, 当编码完成以后并且面临代码测试任务的时候,他们就阅读这些代码并找出它实际上做了什么,把他们的测试工作基于已经写好的代码的基础上。当然,他们无法证明任何事情。所有的这些测试工作能够表明的事情就是编译器工作正常。是的,他们也许能够抓住(希望能够)罕见的编译器Bug,但是他们能够做的仅仅是这些。

反证2:仅仅证明代码做了什么

如果他们首先写好一个详细的规格说明,测试能够以规格说明为基础。代码就能够针对它的规格说明,而不是针对自身进行测试。这样的测试仍然能够抓住编译器的Bug,同时也能找到更多的编码错误,甚至是一些规格说明中的错误。

反证2:仅仅证明代码做了什么

在实践中会出现这样的情况: 一个开发人员要面对测试一个单元时只给出单元的代码而没有规格说明这样吃力不讨好的任务。你怎样做才会有更多的收获,而不仅仅是发现编译器的Bug?第一步是理解这个单元原本要做什么, --- 不是它实际上做了什么。 比较有效的方法是倒推出一个概要的规格说明。这个过程的主要输入条件是要阅读那些程序代码和注释, 主要针对这个单元, 及调用它和被它调用的相关代码。画出流程图是非常有帮助的,你可以用手工或使用某种工具。 可以组织对这个概要规格说明的走读(Review),以确保对这个单元的说明没有基本的错误, 有了这种最小程度的代码深层说明,就可以用它来设计单元测试了。

反证3:我是个很棒的程序员, 我是不是可以不进行单元测试?

  • 在每个开发组织中都至少有一个这样的开发人员,他非常擅长于编程,他们开发的软件总是在第一时间就可以正常运行,因此不需要进行测试。你是否经常听到这样的借口?
  • 在真实世界里,每个人都会犯错误。即使某个开发人员可以抱着这种态度在很少的一些简单的程序中应付过去。 但真正的软件系统是非常复杂的。真正的软件系统不可以寄希望于没有进行广泛的测试和Bug修改过程就可以正常工作。
  • 编码不是一个可以一次性通过的过程。在真实世界中,软件产品必须进行维护以对操作需求的改变作出反应, 并且要对最初的开发工作遗留下来的Bug进行修改。你希望依靠那些原始作者进行修改吗? 这些制造出这些未经测试的原始代码的资深专家们还会继续在其他地方制造这样的代码。在开发人员做出修改后进行可重复的单元测试可以避免产生那些令人不快的负作用。

反证4:不管怎样, 集成测试将会抓住所有的Bug ?

  • 我们已经在前面的讨论中从一个侧面对这个问题进行了部分的阐述。这个论点不成立的原因在于规模越大的代码集成意味着复杂性就越高。如果软件的单元没有事先进行测试,开发人员很可能会花费大量的时间仅仅是为了使软件能够运行,而任何实际的测试方案都无法执行。
  • 一旦软件可以运行了,开发人员又要面对这样的问题: 在考虑软件全局复杂性的前提下对每个单元进行全面的测试。 这是一件非常困难的事情,甚至在创造一种单元调用的测试条件的时候,要全面的考虑单元的被调用时的各种入口参数。在软件集成阶段,对单元功能全面测试的复杂程度远远的超过独立进行的单元测试过程。
  • 最后的结果是测试将无法达到它所应该有的全面性。一些缺陷将被遗漏,并且很多Bug将被忽略过去。
  • 让我们类比一下,假设我们要清洗一台已经完全装配好的食物加工机器!无论你喷了多少水和清洁剂,一些食物的小碎片还是会粘在机器的死角位置,只有任其腐烂并等待以后再想办法。但我们换个角度想想,如果这台机器是拆开的, 这些死角也许就不存在或者更容易接触到了,并且每一部分都可以毫不费力的进行清洗。

反证5:它的成本效率不高

  • 一个特定的开发组织或软件应用系统的测试水平取决于对那些未发现的Bug的潜在后果的重视程度。这种后果的严重程度可以从一个Bug引起的小小的不便到发生多次的死机的情况。这种后果可能常常会被软件的开发人员所忽视(但是用户可不会这样),这种情况会长期的损害这些向用户提交带有Bug的软件的开发组织的信誉,并且会导致对未来的市场产生负面的影响。相反地,一个可靠的软件系统的良好的声誉将有助于一个开发组织获取未来的市场。
  • 很多研究成果表明,无论什么时候作出修改都要进行完整的回归测试,在生命周期中尽早地对软件产品进行测试将使效率和质量得到最好的保证。Bug发现的越晚,修改它所需的费用就越高,因此从经济角度来看, 应该尽可能早的查找和修改Bug。在修改费用变的过高之前,单元测试是一个在早期抓住Bug的机会。
  • 相比后阶段的测试,单元测试的创建更简单,维护更容易,并且可以更方便的进行重复。从全程的费用来考虑, 相比起那些复杂且旷日持久的集成测试,或是不稳定的软件系统来说,单元测试所需的费用是很低的。

反证5:它的成本效率不高

  • 摘自<<实用软件度量>>(Capers Jones,McGraw-Hill 1991),它列出了准备测试,执行测试,和修改缺陷所花费的时间(以一个功能点为基准),这些数据显示单元测试的成本效率大约是集成测试的两倍 系统测试的三倍。

反证5:它的成本效率不高

  • (术语域测试(Field test)意思是在软件投入使用以后,针对某个领域所作的所有测试活动)
  • 这个图表并不表示开发人员不应该进行后阶段的测试活动,这次测试活动仍然是必须的。它的真正意思是尽可能早的排除尽可能多的Bug可以减少后阶段测试的费用。
  • 其他的一些图表显示高达50%的维护工作量被花在那些总是会有的Bug的修改上面。如果这些Bug在开发阶段被排除掉的话,这些工作量就可以节省下来。当考虑到软件维护费用可能会比最初的开发费用高出数倍的时候,这种潜在的对50%软件维护费用的节省将对整个软件生命周期费用产生重大的影响。

反证 结论

  • 经验表明一个尽责的单元测试方法将会在软件开发的某个阶段发现很多的Bug,并且修改它们的成本也很低。在软件开发的后期阶段,Bug的发现并修改将会变得更加困难,并要消耗大量的时间和开发费用。无论什么时候作出修改都要进行完整的回归测试,在生命周期中尽早地对软件产品进行测试将使效率和质量得到最好的保证。 在提供了经过测试的单元的情况下,系统集成过程将会大大地简化。开发人员可以将精力集中在单元之间的交互作用和全局的功能实现上,而不是陷入充满很多Bug的单元之中不能自拔。
  • 使测试工作的效力发挥到最大化的关键在于选择正确的测试策略,这其中包含了完全的单元测试的概念,以及对测试过程的良好的管理,还有适当地使用象AdaTEST和Cantata这样的工具来支持测试过程。这些活动可以产生这样的结果:在花费更低的开发费用的情况下得到更稳定的软件。更进一步的好处是简化了维护过程并降低了生命周期的费用。有效的单元测试是推行全局质量文化的一部分,而这种质量文化将会为软件开发者带来无限的商机。

其他好处 1:减少程序的Bug

  • 要减少软件中的错误数目,方法之一就是拥有一个专业的测试组,其工作就是尽一切可能使软件崩溃。不幸的是,如果拥有测试组,那么即使是经验丰富的开发人员,也会倾向于花费较少的时间来保证代码的可靠性。
  • 软件界有一句俗语:“开发人员不应该测试他们自己的代码”。这是因为开发人员对自己的代码了如指掌,他们很清楚如何采用适当的方法对代码进行测试。尽管这句俗语很有道理,但却忽略了非常重要的一点 - 如果开发人员不对自己的代码进行测试,又如何知道代码能否按照预期的方式运行?
  • 简单说来,他们根本无从得知。开发人员编写那种运行不正常或只在某些情况下运行正常的代码是一个严重的问题。他们通常只测试代码能否在很少的情况下正常运行,而不是验证代码能够在所有情况下均正常运行。

其他好处 2:提高开发速度

  • 在实践工作中,进行了完整计划的单元测试和编写实际的代码所花费的精力大致上是相同的。一旦完成了这些单元测试工作,很多Bug将被纠正,在确信他们手头拥有稳定可靠的部件的情况下,开发人员能够进行更高效的系统集成工作。这才是真实意义上的进步,所以说完整计划下的单元测试是对时间的更高效的利用。而调试人员的不受控和散漫的工作方式只会花费更多的时间而取得很少的好处。
  • 经验表明一个尽责的单元测试方法将会在软件开发的某个阶段发现很多的Bug,并且修改它们的成本也很低。在软件开发的后期阶段,Bug的发现并修改将会变得更加困难,并要消耗大量的时间和开发费用。无论什么时候作出修改都要进行完整的回归测试,在生命周期中尽早地对软件产品进行测试将使效率和质量得到最好的保证。 在提供了经过测试的单元的情况下,系统集成过程将会大大地简化。开发人员可以将精力集中在单元之间的交互作用和全局的功能实现上,而不是陷入充满很多Bug的单元之中不能自拔。

其他好处 3:使程序代码更整洁,优化程序的设计

  • 只有自动的单元测试程序失败时,我们才会去重写代码,在测试驱动开发中,要求我们对程序不停的重构,通过重构,我们可以优化程序的结构设计,消除程序中潜在的错误。同时,为了能够使自己的程序可以很方便的进行测试,开发人员就需要很好地考虑程序的设计,极限编程的方法说可以不需要设计就开始编码,但实际上,它在编写代码的过程中每时每刻都为了方便的进行和通过测试而在优化自己的设计。它实际上是把开始阶段很大很抽象的设计分散到你编写的每个方法中。因此他们会说好设计最后会自然而然的出现。

其他好处 4:编写单元测试代码的过程实际上就是设计程序的过程

  • 在编写单元测试代码时,我们实际上是在思考我们的程序根据预期会返回什么结果,它实际上就是程序设计的过程。而通过重构过程,我们可以对这些设计进行很好的优化。

单元测试的重要性

  • 单元测试是软件测试的基础,因此单元测试的效果会直接影响到软件的后期测试,最终在很大程度上影响到产品的质量。
  • 时间方面
  • 测试效果
  • 测试成本
  • 产品质量

重要性 1:时间方面

  • 如果认真的做好了单元测试,在系统集成联调时非常顺利,因此会节约很多时间,反之那些由于因为时间原因不做单元测试或随便做做的则在集成时总会遇到那些本应该在单元测试就能发现的问题,而这种问题在集成时遇到往往很难让开发人员预料到,最后在苦苦寻觅中才发现这是个很低级的错误而在悔恨自己时已经浪费了很多时间,这种时间上的浪费一点都不值得,正所谓得不偿失。

重要性 2:测试效果

  • 根据以往的测试经验来看,单元测试的效果是非常明显的,首先它是测试阶段的基础,做好了单元测试,在做后期的集成测试和系统测试时就很顺利。其次在单元测试过程中能发现一些很深层次的问题,同时还会发现一些很容易发现而在集成测试和系统测试很难发现的问题。再次单元测试关注的范围也特殊,它不仅仅是证明这些代码做了什么,最重要的是代码是如何做的,是否做了它该做的事情而没有做不该做的事情。

重要性 3:测试成本

  • 在单元测试时某些问题就很容易发现,如果在后期的测试中发现问题所花的成本将成倍数上升。比如在单元测试时发现1个问题需要1个小时,则在集成测试时发现该问题需要2个小时,在系统测试时发现则需要3个小时,同理还有定位问题和解决问题的费用也是成倍数上升的,这就是我们要尽可能早的排除尽可能多的bug来减少后期成本的因素之一。

重要性 4:产品质量

  • 单元测试的好与坏直接影响到产品的质量,可能就是由于代码中的某一个小错误就导致了整个产品的质量降低一个指标,或者导致更严重的后果,如果我们做好了单元测试这种情况是可以完全避免的。
  • 综上所述,单元测试是构筑产品质量的基石,我们不要因为节约单元测试的时间不做单元测试或随便做而让我们在后期浪费太多的不值得的时间,我们也不愿意因为由于节约那些时间导致开发出来的整个产品失败或重来!

四、 C/C++单元测试问答

  • 为什么要进行单元测试?
  • 由谁进行测试?开发部门还是测试部门?
  • 由测试部门进行单元测试为什么成本昂贵?
  • 由开发部门进行单元测试能保证测试效果吗?
  • 边编码边测试会影响编码进度吗?
  • 实施单元测试需要改变开发流程吗?
  • 单元测试测试哪些代码?
  • 实际工作中,单元测试能实现什么程度的测试覆盖?
  • 单元测试如何改良项目代码的整体结构?
  • 我希望依赖全自动的工具来完成单元测试,这一想法现实吗?
  • 如果由开发部门实施单元测试,那么测试部门要做哪些工作?

为什么要进行单元测试?

  • 单元测试保证局部代码的质量
  • 单元测试改良项目代码的整体结构
  • 单元测试降低测试、维护升级的成本
  • 单元测试使开发过程适应频繁变化的需求
  • 单元测试有助于提升程序员的能力

由谁进行测试?开发部门/测试部门?

  • 应该由开发部门进行单元测试!
  • 测试部门进行单元测试的问题:代价高,人手不足,耽误了测试部门对其他测试的准备工作。
  • 由开发部门进行单元测试的问题:担心影响开发进度,程序员不习惯做单元测试,测试自己编写的代码,难于保证测试的效果。
  • 无论由哪个部门做单元测试,都要面对一些问题,但开发部门所面对的问题可以借助工具来解决,而由测试部门进行单元测试,要么无法真正实施,要么代价昂贵。

由测试部门来单元测试成本昂贵?

  • 需多次重复理解程序
  • 反复沟通需要大量时间成本
  • 不利于发挥单元测试对代码结构的约束机制
  • 耽误测试部门对其他测试的准备工作
  • 即使测试部门人手充裕,仅仅从效益来考虑,也不应该由测试部门进行单元测试。如果测试部门本来就人力不充裕(进行单元测试的人员需具备编码能力),勉强由测试部门进行单元测试,结果往往是----没有结果。

由开发部门进行单元测试能保证测试效果吗?

  • 程序员测试自己编写的代码,往往只考虑“正常状况”,这当然会影响测试效果。但如果所用的单元测试工具能够统计各种白盒覆盖率,就能检查测试效果。当然,只做到这一点还是不够的,因为白盒覆盖具有逾后逾难的特点,达到一定的覆盖率后,覆盖率的提升会很困难。如果测试工具功能足够强大,能提供工具帮助用户快速地设计测试用例,达到完整的白盒覆盖,那么测试效果就能得到完全的保证。
  • 实际上,如果没有充分的统计数据,没有达到足够的测试完整性,那么由谁做单元测试,效果都不能保证。

边编码边测试会影响编码进度吗?

  • 传统的单元测试是很费时费力的工作,主要时间消耗在于:编写测试代码、设计测试用例,如果开发工具能自动生成测试代码,并且具有快速设计测试用例的功能,那么测试费时就很少;另一方面,如果测试工具还能提供数据,帮助程序员整理编程思路、快速发现错误,更高效地调试,那么就能大量提高开发效率,抵销测试所消耗的时间,不但不会影响编码进度,甚至加快编码进度。

实施单元测试需要改变开发流程吗?

  • 边开发边测试,单元测试是编码行为而不是测试行为,测试代码看作是项目代码的一部分,程序员提交产品代码时也要提交测试代码和测试报告,其他流程可以不作任何改变。
  • 另一方面,在充分单元测试的基础上,由于具有高质量的局部代码,良好的整体代码结构,保证了代码的可扩展性和可复用性,同时,自动回归测试支持对代码的频繁修改而不用担心引入新的错误,因此,开发流程自然会变得敏捷,可以适应频繁变化的需求,使系统分析、架构设计和后期测试的压力减轻,自然而有效地改进了开发流程。

单元测试测试哪些代码?

  • 单元测试通常不测试很简单的代码,一般也不测试“边界代码”。很简单的代码容易理解,例如Get/Set函数,这里解释一下“边界代码”。“边界代码”是指用于与外部系统交互的代码,例如用于处理用户界面的代码。数据库、文件、网络都可以看作是外部系统,用于读写数据库或文件、或访问网络的代码也可以看作是“边界代码”,这类代码应该独立出来,可以进行单元测试,但对这些代码的单元测试通常不能自动验证预期输出,而是需要人工察看。编程时,不要把普通代码与“边界代码”混在一起,例如,不要把各种运算直接写在界面类中,做到了这一点,绝大多数代码都可以进行单元测试。
    实际工作中,单元测试能实现什么程度的测试覆盖?
  • 单元测试的最低要求是100%语句覆盖,这个覆盖率还是不够的,最好实现多种覆盖的组全,比较理想的覆盖率组合是:100%的语句、条件、分支、路径覆盖,另外,测试工具最好还能自动生成边界测试用例捕捉未处理特殊输入形成的错误。在达到这种覆盖之后,残留的编码错误可以几乎说没有了(设计方面的错误除外,这些属于集成或系统测试的范畴)。

单元测试如何改良项目代码的整体结构?

  • 具有良好整体结构的代码,应该符合“低耦合”的特性,即具有“可测性”。测试不具有“可测性”的代码时一般会产生编译错误,或者需要打桩才能测试,从而将问题暴露出来。发现问题后,重构代码、消除不当耦合一般不难,这种简单的重构将有效地改良代码的整体结构。
    我希望依赖全自动的工具来完成单元测试,这一想法现实吗?
  • 完全自动化是一个美妙的愿望,但由于单元测试的基本特性,完全自动化的单元测试是不现实的。
  • 与其他不同,单元测试是“隔离”的测试,要求代码具有可测性,一个项目甚至一个文件中,难免会有些影响可测性的代码,编译到这些代码时常常会产生编译错误,因此,全自动的单元测试工具往往只能测试小部分代码,即使使用某种技术手段屏蔽掉编译错误,也得不偿失,因为同时也屏蔽掉了改良代码整体结构的宝贵机会。如果采用自底向上的方式,一个一个文件测试,测试一个文件前,先将该文件加入测试工程并编译,没有编译错误时再测试,这样可以及时发现并消除不当耦合,使代码具有可测性,这种非全自动的方式,可以测试绝大多数代码,也保证了代码具有良好的整体结构。
  • 另一方面,主要由测试工具自动生成测试用例来进行测试往往没有实际意义,因为测试工具无法自动了解程序的功能,因此,自动测试用例通常只能发现异常之类的极端错误,大多数一般错误都是无法发现的。测试工具最重要的不是自动生成测试用例,而是能提供快速建立和编辑测试用例的工具。

如果由开发部门实施单元测试,那么测试部门要做哪些工作?

  • 推动、组织单元测试的实施。单元测试既然叫做“测试”,开发部门常常认识不到其重要性和必要性,需要由测试部门推动和协助组织实施。
  • 制定单元测试规范,培训单元测试技术。
  • 检查、审核单元测试结果,保证单元测试的有效性。

五、单元测试工具

  • 测试工具的分类和选择
  • Parasoft
  • Compuware
  • Rational
  • AutomatedQA AQTime
  • xUnit系列
  • Visual Studio 2005
  • Visual Unit

测试工具的分类和选择

  • 白盒测试工具
  • 静态测试工具
  • 动态测试工具
  • 黑盒测试工具
  • 功能测试工具
  • 性能测试工具
  • 测试管理工具
  • 其他测试工具
  • 测试工具的选择

白盒测试工具

  • 白盒测试工具一般是针对代码进行测试,测试中发现的缺陷可以定位到代码级
  • 静态测试工具直接对代码进行分析,不需要运行代码,也不需要对代码编译链接,生成可执行文件。静态测试工具一般是对代码进行语法扫描,找出不符合编码规范的地方,根据某种质量模型评价代码的质量,生成系统的调用关系图等。
  • 动态测试工具与静态测试工具不同,动态测试工具的一般采用“插桩”的方式,向代码生成的可执行文件中插入一些监测代码,用来统计程序运行时的数据。其与静态测试工具最大的不同就是动态测试工具要求被测系统实际运行。

黑盒测试工具

  • 黑盒测试工具适用于黑盒测试的场合,黑盒测试工具包括功能测试工具和性能测试工具。
  • 黑盒测试工具的一般原理是利用脚本的录制(Record)/回放(Playback),模拟用户的操作,然后将被测系统的输出记录下来同预先给定的标准结果比较。黑盒测试工具可以大大减轻黑盒测试的工作量,在迭代开发的过程中,能够很好地进行回归测试。
  • 黑盒测试工具的代表有Rational公司的TeamTest、Robot,Compuware公司的QACenter,另外,专用于性能测试的工具包括有Radview公司的WebLoad、Microsoft公司的WebStress等工具。

测试管理工具

  • 测试管理工具用于对测试进行管理。
  • 内容:对测试计划、测试用例、测试实施进行管理,对缺陷的跟踪管理。
  • 测试管理工具的代表有Rational公司的Test Manager、Compureware公司的TrackRecord,Mercury的TestDirector和Quality Center等软件。

测试工具的选择

  • 功能
  • 基本的功能
  • 报表功能
  • 测试工具的集成能力
  • 操作系统和开发工具的兼容性
  • 价格
  • 连续性和一致性:
  • 全盘考虑,分阶段、逐步的引入测试工具

测试工具引入中的误区分析

  • 没有考虑到公司的实际情况,盲目引入测试工具

专题分析,引入哪些测试工具?

  • 没有进行有效的测试工具的培训

测试工具的培训是一个长期的过程

系列的培训和交流

  • 没有形成一个良好的使用测试工具的环境

没有能够形成一种机制让测试工具真正能够发挥作用

良好的测试工具使用环境

  • 约束机制

测试工具并不是策略

  • 测试工具并不能教测试员如何测试。如果测试出现问题,则测试工具会加重问题。在实现测试过程自动化之前,应首先解决测试过程中的问题。
  • 有些测试工具带有测试策略的建议。但是这种建议很少能够描述得很清楚,并不能针对具体情况,而且往往过于强调他们那种自动化测试的重要性。
  • 工具是辅助性的,关键还是靠人的思想和行为!

Parasoft

  • Parasoft Jtest 代码分析和动态类、组件测试
  • Parasoft C++Test代码分析和动态测试
  • Parasoft .TEST代码分析和动态测试
  • Parasoft Insure++ 实时性能监控以及分析优化
  • Parasoft CodeWizard代码静态分析

是第一个自动化Java单元测试工具。Jtest自动测试任何Java类或部件,而不需要您写一个测试用例、驱动程序或桩函数。只要点击一个按钮,Jtest自动测试代码构造(白盒测试)、测试代码功能性(黑盒测试)、维护代码完整性(回归测试)和静态分析(编程标准执行和指标度量)。不需要复杂的设置,Jtest能够立即使用并指出问题。如果您使用“按合同设计”技术在代码中加入描述信息,Jtest能够自动建立和执行测试用例验证一个类的功能是否符合其功能描述。

Jcontract Java 实时性能监控以及分析优化

Parasoft C++Test

单元测试和静态分析工具,自动测试C和C++类别、功能或组件,而无需编写单个测试实例、测试驱动程序或桩调用。只需点击按钮,C++Test即会采用业内编码标准执行代码的静态分析,测试代码构造(白盒测试),测试代码功能性(黑盒测试),并保持代码完整性(回归测试)。

Parasoft .TEST

单元测试和静态分析工具,自动测试写在Microsoft?.NET框架的类别,而无需编写单个测试场景或桩调用。只需点击按钮,.TEST即会在.NET源代码上自动执行完整系列的静态和动态测试。.TEST RuleWizard性能通过图形化表达希望.TEST在自动编码标准执行过程中查找的模式,支持设计定制的编码标准。

Parasoft Insure++

一个自动化的内存错误、内存泄漏的精确检测工具。Insure++能够可视化实时内存操作,准确检测出内存泄漏产生的根源。Insure++还能执行覆盖性分析,清楚地指示那些代码已经测试过。将Insure++集成到您的开发环境中,能够极大地减少调试时间并有效地防止错误。

Parasoft CodeWizard

高级C/C++源代码分析工具,采用三百种以上行业相关的编码准则,自动识别编译器未检测到的危险的编码构造。CodeWizard可以容易地通过RuleWizard性能,创建新定制的准则,或者抑制用于定制分析的准则。日常使用CodeWizard,可简化代码检查,并使代码更具可读性和可维护性。

Compuware白盒测试工具集

  • http://www.compuware.com
  • BoundsChecker C++,Delphi API和OLE错误检查、指针和泄露错误检查、内存错误检查
  • TrueTime C++ ,Java,Visual Basic 代码运行效率检查、组件性能的分析
  • FailSafe Visual Basic 自动错误处理和恢复系统
  • Jcheck M$ Visual J++ 图形化的纯种和事件分析工具
  • TrueCoverage C++,Java,Visual Basic 函数调用次数、所占比率统计以及稳定性跟踪
  • SmartCheck Visual Basic 函数调用次数、所占比率统计以及稳定性跟踪
  • CodeReview Visual Basic 自动源代码分析工具

Compuware DevPartner Studio

针对软件开发小组使用 Microsoft Visual C++,Microsoft Visual Basic,Java,ASP 或 HTML 设计的一套紧密配合的调试,测试和管理工具。该产品结合了强大的错误检测,性能分析,覆盖率分析,需求管理,测试和发布工具与全面的工程跟踪,错误管理,任务管理和自动的工作流程。DevPartner Studio Enterprise Edition 通过提高软件生产率,提高代码质量,支持工作流以及通讯标准让你对软件工程有更多的控制权。

Win下最好的辅助调试工具。能够帮你检查内存泄漏,GDI泄漏,内存覆盖,数组越界,系统API调用参数是否合适。还能profile,对你的程序的运行时间进行分析,每个函数占用多少运行时间,每一行占用多少运行时间,帮你找出时间的瓶颈。

Rational

  • Rational Purify
  • Rational Quantify
  • Rational PureCoverage
  • IBM Rational PurifyPlus

Rational Purify

面向VC, VB或者Java开发的测试Visual C/C++ 和Java代码中与内存有关的错误,确保整个应用程序的质量和可靠性。在查找典型的Visual C/C++程序中的传统内存访问错误,以及Java中与垃圾内存收集相关的错误方面,Rational Purify可以大显身手。Rational Robot的回归测试与Rational Purify结合使用完成可靠性测试。

网址:http://www-306.ibm.com/software/rational/

Rational Quantify

面向VC、VB 或者Java开发的测试性能瓶颈检测工具,它可以自动检测出影响程序段执行速度的程序性能瓶颈,提供参数分析表等等直观表格。帮助分析影响程序短执行速度的关键部分。

网址:http://www-306.ibm.com/software/rational

Rational PureCoverage

面向VC、VB或者Java开发的测试覆盖程度检测工具,它可以自动检测你的测试完整性和那些无法达到的部分。作为一个质量控制工程,可以使用PureCoverage在每一个测试阶段生产详尽的测试覆盖程度报告。

网址:http://www-306.ibm.com/software/rational/

IBM Rational PurifyPlus

一套完整的运行时分析工具,旨在提升应用的可靠性和性能。包括Purify、Quantify、PureCoverage。

AutomatedQA AQTime

AutomatedQA AQTime是软件项目的测试工具,能实时/静态地分析软件的执行效率和代码性能,发现软件项目客户端和服务器段的瓶颈所在、内存泄漏、消耗资源的代码及未经验证的算法。能够分析Delphi/BCB/VC/VB/GCC等工具开发的软件产品,此外,它有专门For VS.NET的版本。AQTime是专业Windows开发者在开发过程中消除臆测的完全方案,使开发者在完成项目时开发出坚如磐石的程序。通过AQTime无可匹敌的报告系统和测试分析架构,开发这不仅可以得知其项目中确实存在bug和瓶颈,而且会知道具体到哪个模块、类、线程、代码行出了问题,从而快速消除错误。

性能架构和内存分配调试器,适于Win32和.NET互联程序。该工具可以集成到Microsoft Visual Studio .NET中,也可单独使用。通过AQtime 4,您不仅可以发现程序瓶颈,还可以查找瓶颈来源。

AQtime 4是AutomatedQA的获奖产品performance profiling和memory debugging工具集的下一代替换产品,支持Microsoft, Borland, Intel, Compaq 和 GNU编译器。新版本结合了旗舰产品Aqtime和AQtime for .NET的所有优势,前者主要面向Win32应用程序性能分析,而后者则是第一个Microsoft .NET平台的性能和内存分配架构工具。

如同其先前产品一样,AQtime 4可以为.NET和Win32程序生成全面细致的报告,从而帮助您轻松隔离并排除代码中含有的性能问题和内存/资源泄露问题。

xUnit系列

目前的最流行的单元测试工具是xUnit系列框架,常用的根据语言不同分为JUnit(java),CppUnit(C++),DUnit (Delphi ),NUnit(.net),PhpUnit(Php )等等。该测试框架的第一个和最杰出的应用就是由Erich Gamma (《设计模式》的作者)和Kent Beck(XP(Extreme Programming)的创始人 )提供的开放源代码的JUnit。

xUnit系列

  • Aunit Ada http://www.libre.act-europe.fr
  • CppUnit C++ http://cppunit.sourceforge.net
  • ComUnit VB,COM http://comunit.sourceforge.net
  • Dunit Delphi http://dunit.sourceforge.net
  • DotUnit .Net http://dotunit.sourceforge.net
  • HttpUnit Web http://c2.com/cgi/wiki?HttpUnit
  • HtmlUnit Web http://htmlunit.sourceforge.net
  • Jtest Java http://www.junit.org
  • JsUnit(Hieatt) Javas cript 1.4以上 http://www.jsunit.net
  • PhpUnit Php http://phpunit.sourceforge.net
  • PerlUnit Perl http://perlunit.sourceforge.net
  • XmlUnit Xml http://xmlunit.sourceforge.net

CppUnit测试观念

测试的结果是程序直接监测的,而不是“通过人眼对屏幕上的输出结果的观测”。

cppunit并不推荐屏幕输出,或者写可视化的测试单元

测试的过程是自动化的,不需要人工的干预

cppunit推荐用大量典型测试数据进行回归的方式

测试用例是安全可控的。如果一个测试用例错误或者发生了异常,那么应该记录这个错误,并且去执行下一个用例,而不应该停下来。cppunit测试框架保证了这一点。

单元测试是频繁发生的,每天都进行。

由于测试案例的自动化,故此,在你的模块发生了重要改变时(特别是设计上的重大变化/重构时),你都应该马上运行一遍所有的测试程序,以确认你的代码没有引入预期(或曾经出现过)的bug。你可以在准备吃饭的时候,启动单元测试程序进行回归。

单元测试的目的是产生高质量的单元(模块)。从而减少系统集成(包括系统集成测试)的代价。

Visual Studio 2005

Team版的VS2005里面包含了完整的Test功能,具体有:Unit Test,WebTest和LoadTest.这一整套的测试基本涵盖了软件开发会使用到的测试功能.

我这里先从单元测试开始介绍(Unit Test).说起单元测试,很多使用.net进行开发的人员也许马上就想起了NUnit,实际上它是个很好的工具,在VS2005出来之前,我也一直使用.不过现在VS2005已经提供了与NUnit一样,甚至还要强大的功能,我们又有什么理由不使用呢?

微软因该说是很好的领会和贯彻了这个中国的经典儒家思想。Borland的Together好,.Net2005中就集成了了class designer,虽然说功能还不是很强大,同样,在单元测试成为软件开发中必不可少的环节而Nunit好评如潮的时,.Net2005中也加入了自己的单元测试组件。

Visual Studio 2005

自动代码生成,是现代的开发环境追求的目标之一,而visual stutio无疑是走在了业界的最前列。在.net 2005中可以在右键菜单中选择unit test,而后针对该类的方法的单元测试的框架便自动生成,为开发者提供了很大的便利。
Visual Studio 2005
Visual Studio 2005

Visual Unit

  • 国产的单元测试工具,据说申请了多项专利,拥有一批创新的技术。
  • VU目前版本适用于C++语言。
  • 黑盒方面,可以轻松完成功能测试、边界测试、速度测试,
  • 白盒方面,可以轻松完成语句覆盖、条件覆盖、分支覆盖、路径覆盖。这种空前的测试完整性,使代码中的缺陷无所循形。

六、如何实施单元测试

  • 学习基本理论
  • 评估&选择单元测试软件
  • 选择范围:静态分析、动态分析、测试程序框架
  • 开发平台:
  • VC6.0?VS2005
  • QNX

单元测试系列讲座

  • 基本理论
  • CppUnit

七、讨论

2 CppUnit Framework

一、背景

  • CppUnit 是个基于 LGPL 的开源项目,最初版本移植自 JUnit,是一个非常优秀的开源测试框架。
  • CppUnit 和 JUnit 一样主要思想来源于极限编程(XProgramming)。
  • 主要功能就是对单元测试进行管理,并可进行自动化测试。
  • “现象” ? 就应该学习使用这种技术

二、感性认识

  • CppUnit安装与使用方法
  • VC6
  • VS2005
  • QNX
  • CppUnit快速入门

CppUnit VC6.0安装

  • 重要参考:INSTALL-WIN32.txt
  • 解压cppunit-1.12.0.tar.gz
  • Building:---------
    * Open the src/CppUnitLibraries.dsw workspace in VC++.
    * In the 'Build' menu, select 'Batch Build...'
    * In the batch build dialog, select all projects and press the build button.
    * The resulting libraries can be found in the lib/ directory.
  • 注意:直接编译会有错误

CppUnit VC6.0安装

  • 注册插件
  • At the current time, the only supported WIN32 platform is
  • Microsoft Visual C++. You must have VC++ 6.0 at least.
  • Quick Steps to compile & run a sample using the GUI TestRunner:
    - Open examples/examples.dsw in VC++ (contains all the samples).
  • VC7 will ask you if you want to convert, anwser 'yes to all'.
    - Make HostApp the Active project
    - Compile
    - For Visual Studio 6 only:
    - in VC++, Tools/Customize.../Add-ins and macro files/Browse...
    - select the file lib/TestRunnerDSPlugIn.dll and press ok to register
  • the add-ins (double-click on failure = open file in VC++).
    - Run the project

CppUnit VC 设置

  • “Projects/Settings.../C++/C++ Language”页选中“Enable RTTI ”
    RTTI---Run-Time Type Information
  • 在“Projects/Settings.../C++/Code Generation”页选择“Use run-time library”中的内容:
    Release版, 选择"Mulithreaded DLL".
    Debug版, 选择 "Debug Multihreaded DLL".
  • Project Settings/Post-Build step增加: $(TargetPath)

CppUnit VS2005安装

  • 1、 Open examples/examples.dsw in VC++ (contains all the samples).
    VC7 will ask you if you want to convert, anwser 'yes to all'.
  • 2、解决方案上右键,批量编译,选择全部,编译
  • 3、错误:
    #import "libid:80cc9f66-e7d8-4ddd-85b6-d9e6cd0e93e2" version
    ("7.0") lcid("0") raw_interfaces_only named_guids
    改为:
    #import "libid:80cc9f66-e7d8-4ddd-85b6-d9e6cd0e93e2" version
    ("8.0") lcid("0") raw_interfaces_only named_guids
  • 4、d:\cppunit-1.12.0\cppunit-1.12.0\src\msvc6\dsplugin\stdafx.h
    (12) : fatal error C1189: #error : This add-in is for VC++ 6.0
    ?only.
    不用处理

CppUnit VS2005 向导

  • 0. 下载 CPPUnitProjectWizard
  • 1. 复制文件
    CPPUnitProjectWizard.vsdir - 为向导命名
    CPPUnitProjectWizard.vsz - 让VS8知道从哪里找到向导
    到您的Visual Studio 8安装目录下的 VSProjects 文件夹中
  • 2. 把整个CPPUnitProjectWizard解决方案文件夹复制到您的Visual Studio 8安装目录下的VCWizards文件夹中。
    比如,我放在c:\Program Files\Microsoft Visual Studio 8\VC\VCWizards\CPPUnitProjectWizard\CPPUnitProjectWizard
    或者,也可以放在你想放置的其它地方,然后编译CPPUnitProjectWizard.vsz,定义参数 ABSOLUTE_PATH
    Param="ABSOLUTE_PATH = c:\Program Files\Microsoft Visual Studio 8\VC\VCWizards\CPPUnitProjectWizard\CPPUnitProjectWizard"
  • 3. 该项目需要定义环境变量 CPPUNITDIR
    比如,我的环境变量 %CPPUNITDIR% = D:\cppunit-1.12.0
    最后,修改 环境变量 %PATH%,在PATH路径中,增加 %CPPUNITDIR%/lib,以便程序加载时能找到 cppunit_dll.dll
  • 4. 在开发环境中,设置好Include/Lib路径
    %CPPUNITDIR%\Include
    %CPPUNITDIR%\LIB

CppUnit QNX

  • 参考工程模板
  • 修改buildcfg.bat
  • Qnx的安装环境设置
  • QNXDISK:qnx安装在的分区
  • SUNYPRJPATH:工程根路径
  • 调用build.bat编译
  • 举例说明

CppUnit快速入门

  • CppUnit本身的测试
  • Example0--快速入门

三、基本理论

  • 什么是 UnitTest Framework
  • 什么是 CppUnit
  • CppUnit 架构
  • 产品代码与测试代码关系
  • Unit Test与 TDD(测试驱动开发)
  • CppUnit的原理

什么是UnitTest Framework

单元测试框架是编写和运行单元测试的软件工具,用来构建测试、运行测试、报告测试结果

unit test famework 的历史以及 CppUnit
JUnit -> xUnit(含CppUnit )

xUnit 家族

Junit - The reference implementation of xUnit
CppUnit - The C++ port of JUnit
NUnit - The xUnit for .NET
PyUnit - The Python version of xUnit
MinUnit - minimal but functional C Languages unit test framework
XmlUnit – for XML contents

什么是 CppUnit

CppUnit是个基于 LGPL 的开源项目,最初版本移植自 JUnit ,是一个非常优秀的开源测试框架。CppUnit和 JUnit 一样主要思想来源于极限编程(XProgramming)。主要功能就是对单元测试进行管理,并可进行自动化测试。
CppUnit 架构--- namespace
CppUnit 架构--- key classes
CppUnit 架构--- classes to collect test results
CppUnit 架构--- outputter classes for printing test results

产品代码与测试代码关系

产品代码和测试代码的目录结构示意图

产品代码与测试代码关系

产品代码与测试框架关系示意图

Unit Test与 TDD(测试驱动开发)

  • 测试驱动开发精髓
  • TDD循环
    “Test twice, code once” - 测试两次,编码一次。
  • 测试驱动开发的原则

测试驱动开发精髓

  • 维护详尽的程序员编写的测试程序组
  • 除非有相关的测试,否则代码不应被加入产品(“极限编程”,因为测试是重要的,所以对几乎所有代码都要有测试)
  • 测试先行
  • 测试决定你需要写的代码

TDD循环

“Test twice, code once”

  • 编写新代码的测试,查看是否失败
  • 编写新代码,以最简方式实现
  • 再次测试是否成功,重构代码

测试驱动开发的原则

  • 先写测试代码,然后编写符合测试的代码。至少做到完成部分代码后,完成对应的测试代码;
  • 测试代码不需要覆盖所有的细节,但应该对所有主要的功能和可能出错的地方有相应的测试用例;
  • 发现 bug,首先编写对应的测试用例,然后进行调试;
  • 不断总结出现 bug 的原因,对其他代码编写相应测试用例;
  • 每次编写完成代码,运行所有以前的测试用例,验证对以前代码影响,把这种影响尽早消除;
  • 不断维护测试代码,保证代码变动后通过所有测试;
  • 在编码前:他可以强迫你对需求进行详细的分析。
  • 在编码时:他可以使你对over coding保持警觉。
  • 在重构时:可以确保新的设计能够兼容旧版本的功能。
  • 在团队开发时:可以确保自己的单元是无误的。

CppUnit的原理

  • Test
  • TestFixture
  • TestCase
  • TestSuite
  • ASSERT

CppUnit的原理--- Test

  • //Test.h
  • 测试类的抽象基类
  • 规定了所有测试类都应该具有的行为
  • 对应于Composite Pattern中的Component

CppUnit的原理--- TestFixture

  • //TestFixture.h
  • 一个或一组测试用例的测试对象被称为 Fixture
  • Fixture就是被测试的目标
  • 为一组相关的测试提供运行所需的公用环境
  • 抽象类,用于包装测试类使之具有setUp方法和tearDown方法。

CppUnit的原理--- TestCase

  • //TestCase.h,TestCase.cpp
  • 多重继承:
  • TestCase : public TestLeaf, public TestFixture
  • TestLeaf: public Test
  • TestCase :对这个 Fixture 的某个功能、某个可能出错的流程编写测试代码,这样对某个方面完整的测试被称为TestCase(测试用例)

CppUnit的原理--- TestCase步骤

  • 对 fixture 进行初始化,及其他初始化操作,比如:生成一组被测试的对象,初始化值;( setUp ())
  • 按照要测试的某个功能或者某个流程对 fixture 进行操作;
  • 验证结果是否正确;
    对 fixture 的及其他的资源释放等清理工作 ( tearDown())

运行时 CppUnit 会自动为每个测试用例函数运行 setUp,之后运行 tearDown,这样测试用例之间就没有交叉影响

CppUnit的原理--- TestCase注意点

  • 可以自动执行,不用人手操作。
  • 自动返回测试结果。
  • 绝对的独立,不能与其他TestCase有任何联系。就算测试同一个函数的不同功能也需要分开。每个TestCase可以说是一个孤岛。
  • 例如
    CppUnit的原理--- TestCase例子
    CppUnit的原理--- ASSERT
  • CPPUNIT_ASSERT(condition):判断condition的值是否为真,如果为假则生成错误信息。
  • CPPUNIT_ASSERT_MESSAGE(message, condition):与CPPUNIT_ASSERT类似,但结果为假时报告messsage信息。
  • CPPUNIT_FAIL(message):直接报告messsage错误信息。
  • CPPUNIT_ASSERT_EQUAL(expected, actual):判断expected和actual的值是否相等,如果不等输出错误信息。
  • CPPUNIT_ASSERT_EQUAL_MESSAGE(message, expected, actual):与CPPUNIT_ASSERT_EQUAL类似,但断言失败时输出message信息。
  • CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, actual, delta):判断expected与actual的偏差是否小于delta,用于浮点数比较。
  • CPPUNIT_ASSERT_THROW(expression, ExceptionType):判断执行表达式expression后是否抛出ExceptionType异常。
  • CPPUNIT_ASSERT_NO_THROW(expression):断言执行表达式expression后无异常抛出。

四、 核心内容

  • 测试对象(Test,TestFixture,...)
    用于开发测试用例,以及对测试用例进行组织管理
  • 测试结果(TestResult)
    处理测试用例执行结果, Observer Pattern
  • 测试结果监听者(TestListener)
    TestListener作为TestResult的观察者,担任实际的结果处理角色
  • 结果输出(Outputter)
    将结果进行输出,可以制定不同的输出格式
  • 对象工厂(TestFactory)
    用于创建测试对象,对测试用例进行自动化管理
  • 测试执行体(TestRunner)
    用于运行一个测试
    核心内容 ---Test
  • 所有测试对象的基类
  • CppUnit采用树形结构来组织管理测试对象,类似于目录树

组合设计模式(Composite Pattern),Test的两个直接子类TestLeaf和TestComposite分别表示“测试树”中的叶节点和非叶节点,其中TestComposite主要起组织管理的作用,就像目录树中的文件夹,而TestLeaf才是最终具有执行能力的测试对象,就像目录树中的文件。

Test最重要的一个公共接口为:
virtual void run(TestResult *result) = 0;
其作用为执行测试对象,将结果提交给result。

在实际应用中,一般不会直接使用Test、TestComposite以及TestLeaf,除非要重新定制某些机制。

核心内容 ---TestFixture

用于维护一组测试用例的上下文环境

在实际应用中,经常会开发一组测试用例来对某个类的接口加以测试,而这些测试用例很可能具有相同的初始化和清理代码。为此,CppUnit引入TestFixture来实现这一机制。

TestFixture具有以下两个接口,分别用于处理测试环境的初始化与清理工作:
virtual void setUp();
virtual void tearDown();

核心内容 ---TestCase

测试用例,从名字上就可以看出来,它便是单元测试的执行对象。
TestCase从Test和TestFixture多继承而来,通过把Test::run制定成模板函数(Template Method)而将两个父类的操作融合在一起

这里要提到的是函数runTest,它是TestCase定义的一个接口,原型如下:
virtual void runTest();

用户需从TestCase派生出子类并实现runTest以开发自己所需的测试用例。

核心内容 ---TestSuit

测试包,按照树形结构管理测试用例

TestSuit是TestComposite的一个实现,它采用vector来管理子测试对象(Test),从而形成递归的树形结构。

核心内容 --- TestCaller

TestCase适配器(Adapter),它将成员函数转换成测试用例

虽然可以从TestCase派生自己的测试类,但从TestCase类的定义可以看出,它只能支持一个测试用例,这对于测试代码的组织和维护很不方便,尤其是那些有共同上下文环境的一组测试。为此,CppUnit提供了TestCaller以解决这个问题

TestCaller是一个模板类,它以实现了TestFixture接口的类为模板参数,将目标类中某个符合runTest原型的测试方法适配成TestCase的子类。

在实际应用中,大多采用TestFixture和TestCaller相组合的方式,详见后面的例子

核心内容 ---TestResult和TestListener

处理测试信息和结果

TestResult和TestListener采用了观察者模式,TestResult维护一个注册表,用于管理向其登记过的TestListener,当TestResult收到测试对象(Test)的测试信息时,再一一分发给它所管辖的TestListener。这一设计有助于实现对同一测试的多种处理方式。

核心内容 ---TestFactory

测试工厂

辅助类,通过借助一系列宏定义让测试用例的组织管理变得自动化。参见后面的例子

核心内容 --- TestRunner

用于执行测试用例

TestRunner将待执行的测试对象管理起来,然后供用户调用。其接口为:
virtual void addTest( Test *test ); virtual void run( TestResult &controller, const std::string &testPath = "" );

这也是一个辅助类,需注意的是,通过addTest添加到TestRunner中的测试对象必须是通过new动态创建的,用户不能删除这个对象,因为TestRunner将自行管理测试对象的生命期
  核心内容 ---例1
  核心内容 ---例1
  核心内容 ---例1
  核心内容 ---例2
  核心内容 ---例2
  核心内容 ---例2
  核心内容 ---例3
  核心内容 ---例3
  核心内容 ---例3

五、三个例子分析

Example 1: 自己写测试框架
Example 2: 用CppUnit改写Exp1
Example 3:完整的例子 ,用HelperMacros
Example 1: 自己写测试框架
Unit Test Frameworks
  步骤0:建立单元测试框架
  步骤1:建立单元测试 (TDD第一次测试 )
  步骤2:正确建立Book (TDD编写一次)
  步骤3:再次测试 (TDD第二次测试)
Exp1--- 0建立单元测试框架
Exp1--- 0建立单元测试框架
Exp1--- 0建立单元测试框架
Exp1--- 1建立单元测试 (Test 1st)
Exp1--- 1建立单元测试 (Test 1st)
Exp1--- 1建立单元测试 (Test 1st)
Exp1--- 3再次测试 (Test 2nd)
Example 2: 用CppUnit改写Exp1
  1:使用CppUnit框架的TestCase替换自定义的UnitTest
  2 :如果需要运行多个测试而不是放在单个runTest()中,则需要引入TestFixture
  3 :使用 TestSuite, 把main()中的addTest()转移到 suite()中
  4 :由于对每个测试类都要重复编写suite()静态函数,容易出错,所以使用Helper Macros来替换手工编写suite()静态函数,和注册函数
Exp2--- 1使用TestCase
Exp2--- 2使用TestFixture
Exp2--- 2使用TestFixture
Exp2--- 2使用TestFixture
Exp2--- 2使用TestFixture
Exp2--- 2使用TestFixture
Exp2--- 3使用TestSuite
Exp2--- 3使用TestSuite
Exp2--- 4使用HelperMacros
Exp2--- 4使用HelperMacros
Example 4:完整例子
  CppUnit Cookbook
  Example3-1
  Example3-2

六、CppUnit源码解读

  • 参考“CppUnit源码解读.doc”

七、讨论


火龙果软件/UML软件工程组织致力于提高您的软件工程实践能力,我们不断地吸取业界的宝贵经验,向您提供经过数百家企业验证的有效的工程技术实践经验,同时关注最新的理论进展,帮助您“领跑您所在行业的软件世界”。
资源网站: UML软件工程组织