11/09/2008

Notes: "The Art of Software Testing" - 2

The Art of Software Testing


Part II - 如何进行测试


一、人工测试:阅读代码

1. 代码检查(code inspection)

  1) 活动人员组成:协调人(Drive "Preparing -> Recording -> Ensure Fixing" Process),代码拥有者(Code Owner),其它开发组员(Other Dev Team Member).
  2) 活动内容:逐条讲述/解释代码,其他人提做判断、提意见;对照常见错误列表分析程序.
  3) 会议内容集中于查找错误,而不是改正错误.
  4) 适当控制时间,消耗脑力的会议越长时间越短.
  5) 会后协调人要确保错误被修复,总结错误教训,避免组员犯类似错误.
  6) 会议参与者要对事不对人,代码拥有者要以平和心态接受意见,其它参与者以建设性的态度参与讨论.
  7) 代码检查是个团队内部相互学习的良好机会(编程风格,算法选择,编程技巧等).
  8) 代码检查的最大优势在于尽早发现错误,降低修改成本.
  
2. 代码走查(code walk through)

  1) 与代码检查不同的地方在于,这里使用的是由人去动态模拟程序运行、检查程序代码正确性.
  2) 全体参会人员用预先选好的数据沿着程序逻辑走一遍,在白板上记录状态和变化.
  3) 为了确保会议的顺利进行,应坚持一个原则:软件中存在的错误不应被视为编写程序的人员自身的弱点,而应被看作软件开发本身的困难.

3. 常见错误检查列表(Code Error Check List)
  
  数据错误
  1) 变量需要在引用前初始化.
  2) 是否确保数组下表不越界.
  3) 指针变量的值和被指对象的生命周期是否一致(dangling reference).
  4) 动态分配内存、对象的生命周期管理是否清晰、恰当.
  5) 是否考虑了内存地址对齐的问题.
  6) 动态类型转化是否安全(将内存对象解释成另外一个对象类型).
  7) 字符处理时,是否考虑了最后结尾的字符的问题.
  8) 实现类中,是否完成了所有的继承需求.
  9) 变量名是否合理而清晰.

  运算错误
  1) 进行运算的两个数据的类型是否是所期望的.
  2) 混合类型的数据进行运算时是否有清晰的说明.
  3) 是否考虑了运算(中间或者最终结果)可能导致的溢出问题.
  4) 运算结果赋值时,目标变量是否拥有足够的容纳量.
  5) 运算的顺序是否明确清晰.
  6) 除法中除数是否考虑除数可能为0的情况.

  比较错误
  1) 进行比较的数据类型是否一致,特别要注意有符号与无符号数的比较.
  2) 注意在引入“非”运算符号的时候语句的语义是否正确.
  3) 用作逻辑判断的结果是否是布尔型的值.
  4) 复杂的逻辑运算是否确保了清晰正确的顺序.
  5) 是否注意了逻辑运算中的side effect和bypass evaluation.
  
  流程错误
  1) 循环语句中循环的次数是否确保正确.
  2) 是否对输入的数据进行了假设,这个假设是否体现在输入参数检查和处理中.
  3) 跳转语句(Goto, Break, Continue)是否执行了正确而清晰的语义.
  4) for语句的循环体是否包含了所有想要的语句(忘记{}会引发意外错误).

  函数错误
  1) 是否考虑了参数的压栈顺序与调用风格(cdecl, stdcall).
  2) 是否考虑了传递参数时中间临时对象的创建管理的开销.
  3) 传递内存块时,是否考虑了内存对象的生命期管理问题.
  4) 是否确保没有将函数局部变量(或者局部变量的指针、引用)返回给调用者.
  5) 函数所有的返回路径是否都能正确处理资源释放和临时对象生命周期管理问题.
  6) 在调用函数时,是否考虑了处理所有的返回值和异常情况.

  I/O错误
  1) 打开创建文件时的模式、权限的设置是否正确.
  2) 新建文件时是否确保所有的父目录会正确创建.
  3) 文件是否及时清空了缓存.
  4) 是否考虑了I/O系统出错的情况.
  5) 是否确保文件会正确的关闭.
  6) 文件结束的判断是否正确.

  其它问题
  1) 多线程的问题是否考虑了thread safety.
  2) 不同模块内的全局变量是否考虑了初始化顺序问题.
  3) 交叉引用其它模块的全局变量时,变量声明和定义处的类型是否一致.
  4) 引用其它模块的变量时,能否保证引用时变量已经初始化.
  5) 是否消除了所有编译警告.
  6) 进程间同步时是否考虑了可能的死锁问题.

二、机器测试:运行程序

1. 软件开发流程与测试的角色
  
  1) 典型的软件开发过程:需求分析->[外部规范说明]->系统设计->[系统架构说明]->软件系统设计->[模块接口规范说明]->编码开发  
  2) 对代码的测试,依照模块接口规范进行,称之为"模块测试"(单元测试).
  3) 对整个最终系统按照外部规范说明进行的测试,称之为"功能测试".
  4) 系统开发过程中的测试,大致可以分为:验收测试、系统测试、功能测试、模块测试等.

2. 测试的分类

  1) 验收测试(Acceptance Testing):验收测试是由最终用户进行根据双方签署的合同进行的测试,以检测交付的系统是否满足合同说明.

  2) 功能测试(Function Testing):功能测试是一个试图发现系统和外部规范说明不一致的过程.外部规范说明是一份从最终用户的角度对程序行为的描述.

  3) 系统测试(System Testing):是对一个系统的非功能性方面进行测试.系统规范说明不能作为系统测试的基础,往往用户手册和系统目标文档,才是进行系统测试的基础.系统测试主要包括:

  • a) 容量测试(Volume Testing):是系统经受大数据量的考验.注意这里只强调数据的容量,没有强调时间和其它因素.
  • b) 压力测试(Stress Testing):使系统接受高负载,或者高强度的检验.注意,这里的负载和强度都和时间有关,指的是单位时间内到达的数据或者操作的数量.
  • c) 性能测试(Performance Testing):测试系统在某一用户负载和特定环境配置的条件下的响应时间,吞吐率[throughput](带宽[bandwidth], 每秒操作数[IOPS]).
  • d) 易用性测试(Usability Testing):通常是测试用户使用最终系统是否方便,容易.但是这个测试相对会显得比较主观.易用性测试通常包括衡量完成一件事所需的步骤, 用户用到了多少系统提供的功能, 所有文字输出信息是否语法正确且风格一致,系统是否给予了用户恰当的提示信息等等.
  • e) 安全性测试(Security Testing):主要包括用户的秘密信息(银行帐号,密码)是否被安全地传送,用户的私密信息(偏好,个人隐私)是否被暴露和不恰当收集.
  • f) 兼容性测试(Compatibility Testing):与已有的相关组件,程序是否能很好地配合.对上一版本的系统和软件产生的数据文件是否能正确地识别和转换等等.
  • g) 可恢复测试(Recovery Testing):有很多系统(比如数据库,中间件)都有容错,可恢复功能,能从一些程序错误,硬件失效总回复过来急需工作.需要有一些测试来验证这些功能确实能够正确地工作.
  • h) 可靠性测试(Reliability Testing):有些软件系统(比如航天控制)需要很高的可靠性,因此需要对系统进行相应的测试.但是,通常这样的测试比较难,特别是对高可靠性的系统而言.
  • i) 配置测试(Configuration Testing):系统运行的相关软硬件环境会有多种,应该针对不同的环境测试系统的表现.
  • j) 安装测试(Installation Testing):现代软件的安装往往比较复杂,软件提供方需要对各种情况下的软件安装过程进行测试,以保证其准确无误.
  • k) 国际化测试:主要测试软件在不同的语言配置和环境下是否能正确工作,是否能显示当地的语言,是否能符合当地的时区,用语习惯,货币单位等等.


  4) 单元测试

  • a) 特点:规模小,容易定位错误;方便并行开发测试不同的模块;分离了关注单位的大小,使每个人关注的内容有限.
  • b) 单元测试是一个白盒测试,在现代软件开发中,通常是开发人员完成单元测试.
  • c) 由于各模块单元需要相互调用,因此整个系统有两种方式完成测试:增量方式和非增量方式.前者对模块进行拓扑排序,当所有被依赖的模块都准备好之后才进行后续的测试;后者则需要各模块的开发测试者编写相应的mock/stub模块方便本模块的测试.
  • d) 除了可以减少依赖,方便并行测试,增量模式在其它方面都比非增量模式更有利于系统的开发.
  • e) 增量测试可以按照自上而下和自下而上两种方式进行.自上而下的方法最大有点在于早期演示,激发积极性;缺点在于搭建测试环境比较困难.自下而上的方法则刚好相反.
  • f) 由于单元测试通常需要不断进行,因此辅助的自动化工具非常重要,可以有效提高工作效率.


3. 测试的流程

  1) 测试的计划, 测试计划通常应该包括:

  • 测试目标: 明确的目标可以帮助团队更高效地达到目标.
  • 结束准则: 可以更好地帮组我们明确工作的目标,提高管理的效率.
  • 进度安排: 对细分的工作任务要有明确的时间进度计划.
  • 责任划分: 团队成员的任务要有明确的安排,避免出现责任不明而导致的争端.
  • 测试工具: 测试是需要高度自动化,专业化的工作.相应测试工具的准备,使用都需要计划.
  • 硬件配置: 这是完成真正的测试任务所需资源很重要的一部分.
  • 集成方法: 在多模块的大型系统中,采用什么策略进行系统集成和测试.
  • 测试用例: 测试用例构造的依据和方法.


  2) 测试结束的准则

  • 根据代码覆盖率:当测试用例对被测程序的代码覆盖率达到一定标准时就停止.
  • 根据测试用例覆盖率:根据一些系统方法找出来的用例都跑完之后.
  • 更具用例的数目和发现错误的数目:如果找到的错误达到一定的数目,或者用例达到一定的数据.
  • 根据错误发现和修正的变化趋势:如果单位时间里发现的错误数目在不断下降,或者持续处于低水平,那么继续测试的动力不大.

No comments: