Developer Tesing 开发者测试
Role of Developer Testing
开发者测试在软件质量中的角色
测试的目标是找出错误,成功的测试应该弄垮软件
测试永远不可能彻底证明程序中没有错误
测试本身不能改善软件质量
测试时要求你假设会在代码里找到错误
Recommended Approach to Developer Testing
开发者测试的推荐方法
要点
- 对每一项相关的需求进行测试,以确保需求都已经被实现
- 对每一个相关的设计关注点进行测试,以确保设计已经被实现
- 用基础测试来扩充针对需求和设计的详细测试用例
- 使用一个检查表,其中记录着你在本项目迄今为止所犯的,以及在过去的项目中所见的错误类型
Test First or Test Last
测试先行还是测试后行
- 首先写测试用例可将从引入缺陷到发现缺陷之间的时间缩减至最短
- 并不比后测试多花功夫,只是调整了下工作顺序而已
- 可更早发现缺陷,同时也更更容易修正它们
- 将迫使你在开始写代码之前至少思考一下需求和设计,而这往往会催生更高质量的代码
- 更早地把需求上的问题暴露出来,对于一个糟糕的需求来说,要写出测试用例是一件困难的事情
Limitations of Developer Testing
开发者测试的局限性
- 开发者测试倾向于“干净测试”
- 开发者测试往往会忽略一些更复杂的测试覆盖率类型
Bag of Testing Tricks
测试技巧锦囊
Incomplete Testing
不完整的测试
- 由于进行完全测试实际上是不可能的,因此测试的窍门就在于选择那些最有可能找到错误的测试用例
Structured Basis Testing
结构化的基础测试
- 你需要去测试程序中每一条语句至少一次
Data-Flow Testing
数据流测试
-
数据的状态
-
已定义 defined
- 已初始化,但还没有使用
-
已使用 used
- 已用于计算,或参数
-
已销毁 killed
-
已进入 entered
- 控制流已进入一个子程序,但还没使用该变量
-
已退出 exited
- 在对变量产生影响之后,控制流立即层出子程序
-
-
数据状态的组合
-
已定义-已定义
- 在对变量赋值前将其定义了两次,那么 你需要的不是更好的程序,而是一台更好的计算机!
-
已定义-已退出
- 如果是局部变量,完全没有理由在定义后却不使用就退出。如果是一个子程序的参数或全局变量,还说得过去
-
已定义-已销毁
- 这意味着要么这个变量对程序没使用意义,要么就是程序员忘记写使用这个变量的代码了
-
已进入-已销毁
- 如果这是一个局部变量,那就有问题了。如果局部变量未定义或者未使用,那么它完全不需要被销毁
- 如果这是一个子程序员的参数或全局变量,只要在销毁之前确定该变量 已经在别的什么地方定义过,那么就没有问题
-
已进入-已使用
- 如果这是一个局部变量就有问题,这个变量应该在使用之前被定义
- 反之与上面 相同
-
已销毁-已销毁
- 一个变量不应该被销毁两次
-
已销毁-已使用
- 使用一个已销毁的变量,从逻辑上就讲不通
-
已使用-已定义
- 检查一下这个变量之前是否已定义过非常有必要
-
Equivalence Partitioning
等价类划分
Error Guessing
猜测错误
Boundary Analysis
边界值分析
Classes of Bad Data
几类坏数据
- 数据太少(没有) or 太多
- 错误的数据情况
- 程度错误的数据
- 未初始化的数据
Classes of Good Data
几类好数据
- 正常的情形
- 最小 or 最大的正常局面
- 与旧数据的兼容性
Typical Errors
典型错误
Which Classes Contain the Most Errors
哪些类包含最多的错误
- 80%的错误存在于项目20%的类或子程序中
- 50%的错误被发现存在于项目5%的类当中
- 项目中20%的子程序占用了80%的开发成本。错误并非平均分布在所有的子程序中,而是集中在少数几个子程序里。
- 如果能避免卷入到那些 烦人的子程序中,那么 你就可省下近80%的成本,从而节约一大段开发时间
Errors by Classification
错误的分类
-
多数错误的影响范围是相当有限的
-
许多错误发生在构建的范畴之外
- 相互矛盾的需求
-
多是的构建期错误是编程人员的失误造成的
-
笔误是常见的问题根源
-
错误理解设计会经常出现
-
多数错误很容易修正
Debugging 调试
Overview of Debugging Issues
调试概述
Role of Debugging in Software Quality
调试在软件质量中所扮演的角色
- 调试本身并不是改进代码质量的方法,而是诊断代码缺陷的一种方法。软件的质量必须从开始逐步建立:开发高质量软件产品的最佳途径是精确描述需求,完善设计,并使用高质量的代码编写规范。调试只是迫不得已时采用的手段
Variations in Debugging Performance
调试效率的巨大差异
- 最好的程序员弪找出最多的错误,最快的找出错误,且往往能正确改正错误。
- 提高软件质量能减少开发成本
Defects as Opportunities
让你有所收获的缺陷
-
理解你正在编写的程序
-
明确你犯了哪种类型的错误
-
从代码阅读者角度分析代码质量
-
审视自己解决问题的方法
-
审视自己修正缺陷的方法
- 可读性、设计、软件质量。编写优秀代码所得到的回报,如果你精于此道,你甚至无须频繁调试
An Ineffective Approach
一种效率低下的调试方法
- 凭猜测找出缺陷
- 不要把时间浪费在理解问题上
- 用最唾手可得的方式修正错误
Finding a Defect
寻找缺陷
The Scientific Method of Debugging
科学的调试方法
-
经典科学调试步骤
-
- 通过可重复的试验收集数据
-
- 根据相关数据的统计构造一个假说
-
- 设计一个实验来证明或反证这个假说
-
- 证明或反之假说
-
- 根据需要重复上面步骤
-
-
寻找缺陷的有效方法
-
- 将错误状态稳定下来
-
-
确定错误的来源
- a. 收集产生旗帜鲜明的相关数据
- b. 分析所收集的数据,并构造对缺陷的假设
- c. 确定怎样去证实或证伪这个假设,可对程序进行测试或是通过检查代码
- d. 按照2(c)确定的方法对假设做出最终结论
-
-
- 修补缺陷
-
- 对所修补的地方进行测试
-
- 查找是否还有类似的错误
-
Tips for Finding Defects
寻找缺陷的一些小建议
-
在构造假设时考虑所有的可用数据
-
提炼产生错误的测试用例
-
在自己的单元测试族中测试代码
-
利用可用的工具
-
采用多种不同的方法重现错误
- 有时,尝试一些能产生相似错误,而本身又不尽相同的测试用例或许能让调试过程柳暗花明
-
用更多的数据生成更多的假设
-
否定性测试用例的结果
-
对可能的假设尝试头脑风暴
-
在桌上方一个计事本,把需要尝试的事情逐条列出
-
缩小嫌疑代码的范围
- 可用二分查找法
-
对之前出现过缺陷的类和子程序保持警惕
-
检查最近修改过的代码
-
扩展嫌疑代码的范围
-
增量式集成
-
检查常见缺陷
-
同其他人讨论问题
-
抛开问题,休息一下
-
Brute-Force Debugging 蛮力调试
Fixing a Defect
修正缺陷
在动手前先要理解问题
理解程序本身,而不仅仅是问题
验证对错误的分析
放松一下
-
对于系统有关键影响的缺陷
- 匆忙动手解决是你所能做的最低效的事情之一
- 休息足够长时间能让你肯定自己的解决方案是对的,不要受到所谓捷径的诱惑
保存最初的源代码
治本,而不是治标
修改代码时一定要有恰当的理由
一次只做一个改动
检查自己的改动
增加能暴露问题的单元测试
搜索类似的缺陷
Code-Tuning Techniques 代码调整技术
本章可称为“反重构”,以牺牲程序内部结构的某些特性来换取更高的性能
Logic
逻辑
Stop Testing When You Know the Answer
在知道答案后停止判断
- 即短路求值
Order Tests by Frequency
按照出现频率来调整判断顺序
Compare Performance of Similar Logic Structures
相似逻辑结构之间的性能比较
Substitute Table Lookups for Complicated Expressions
用查询表替代复杂表达式
Use Lazy Evaluation
使用惰性求值
Loops
循环
Unswitching
将判断外提
- 在循环内做判断
for(...){
if(...){...}
else{...}
}
- 将判断外提 if(...){ for(...){} } else{ for(...){} }
Jamming
合并
- 把两个相同一组元素进行操作的循环合并在一起
Unrolling
展开
Minimizing the Work Inside Loops
尽可能减少在循环内部做的工作
Sentinel Values
哨兵值
Putting the Busiest Loop on the Inside
把最忙的循环放在最内层
Strength Reduction
削减强度
Data Transformations
数据变换
Use Integers Rather Than Floating-Point Numbers
使用整型数而不是浮点数
Use the Fewest Array Dimensions Possible
数组纬度尽可能少
Minimize Array References
尽可能减少数组引用
Use Supplementary Indexes
使用辅助索引
Use Caching
使用缓存机制
Expressions
表达式
利用代数恒等式
-
not a and not b not (a or b)
- 如果选择第二个表达式,你就避免了一次not
削弱运算强度
编译其初始化
- 如果在一个子程序调用中使用了一个具名常量或神秘数值,且它是子程序唯一的参数,这就是一种暗示,你应当提前计算这一数值,把它放到某个常量中
预先算出结果
Routines
子程序