【软件构造】第七章知识整理
健壮性和正确性
***健壮性:系统在不正常输入或不正常外部环境下仍能够表现正常的程度
***面向健壮性的编程:
处理未期望的行为和错误终止
即使终止执行,也要准确/无歧义的向用户展示全面的错误信息
错误信息有助于进行debug
***编程原则:总是假定用户恶意、假定自己的代码可能失败
把用户想象成白痴,可能输入任何东西
封闭实现细节,限定用户的恶意行为
考虑极端情况,没有“不可能”
***正确性:程序按照spec加以执行的能力,是最重要的质量指标!
***正确性VS健壮性:
正确性:永不给用户错误的结果,倾向于直接报错,直接结束
健壮性:尽可能保持软件运行而不是总是退出,倾向于容错,程序内部已有容错机制
***对外的接口,倾向于健壮;对内的实现,倾向于正确
Throwable
一个异常类是从Throwable分离出来的类
Runtime异常、其他异常
Exception可分为两类:运行时异常 + 其他异常
***运行异常:由程序员在代码里处理不当造成
如果在代码中提前进行验证,这些故障就可以避免
e.g:可以通过检查数组的下标是否超过数组的长度以避免ArrayIndexOutOfBoundsException
可以通过检查变量是否为空以避免NullPointerException
***其他异常:由外部原因造成
即使在代码中提前加以验证,也无法完全避免失效发生。
Checked异常、Unchecked异常
***Unchecked exception:Error + RuntimeException
不需要在编译的时候用try…catch等机制处理
可以不处理,编译没问题,但执行时出现就导致程序失败
类似于编程语言中的dynamictype checking
***Checked exception:Throwable除去unchecked exception
必须捕获并指定错误处理器handler,否则编译无法通过
类似于编程语言中的static typechecking
Checked Exception 和 Unchecked Exception的区别
Checked异常的处理机制
用于exception的处理机制的五个关键字:try、catch、finally、throws、throw
throws:声明“本方法可能会发生xxx异常”
throw:抛出xxx异常
try、catch、finally:捕获并处理xxx异常
***方法要在定义和spec中明确声明所抛出的全部checked exception,以便调用该方法的client加以处理
***处理异常时,尽量在自己这里处理,实在不行就往上传
***释放资源:
***清理现场:printStackTrace()
自定义异常类
如果JDK提供的exception类无法充分描述你的程序发生的错误,可以创建自己的异常
自定义异常类的编写:
自定义异常类的使用:
***在抛出异常的时候,将现场信息记入异常
在异常处理时,利用这些信息给用户更有价值的帮助
断言的作用、应用场合
***断言:在开发阶段的代码中嵌入,检验某些“假设”是否成立。若成立,表明程序运行正常,否则表明存在错误。即是对代码中程序员所做假设的文档化,也不会影响运行时性能(在实际使用时,assertion 都会被disabled)
e.g
***两种格式:①assert condition; ②assert condition :message;
***为什么使用断言:
①记录和测试程序员的假设条件(如不变量是否符合条件)
②验证程序员是否理解正确
③快速发现错误
④增加程序的可信度
⑤假设将黑盒测试转换为白盒测试
使用断言的主要目的是为了在开发阶段调试程序、尽快避免错误
***断言用在哪里?
用于验证内部不变量/表示不变量/控制流不变量/方法的前置条件/方法的后置条件
***断言主要用于开发阶段,避免引入和帮助发现bug;实际运行阶段,不再使用断言
避免降低性能
程序之外的事,不受你控制,不要乱断言!!!
调试的基本过程和方法
***过程:
reproduce:找到一种可靠且方便的可以根据需求重现问题的方法
diagnose:构建假设,并通过实验来测试它们,直到已确定错误的根源
fix:设计和实施解决问题的方法,并在此过程中保持软件的整体质量
reflect:通过该错误得到哪些教训?
***方法:假设-检验
***从最小的测试用例开始复现错误
黑盒测试用例的设计
***黑盒测试:对程序外部表现出来的行为的测试,用于检查代码的功能,不关心内部实现细节
***基于等价类划分的测试:将被测函数的输入域划分为等价类,从等价类中导出测试用例。
针对每个输入数据需要满足的约束条件,划分等价类
每个等价类代表着对输入约束加以满足/违反的有效/无效数据的集合
等价类基于的假设:相似的输入,将会展示相似的行为。故可从每个等价类中选一个代表作为测试用例即可
***边界值分析方法是对等价类划分方法的补充,这是由于大量的错误发生在输入域的“边界”而非*,因此在等价类划分时,将边界作为等价类之一加入考虑,此外还要考虑边界的两侧
以注释的形式撰写测试策略
测试策略(根据什么来选择测试用例)非常重要,需要在程序中显式记录下来
目的:在代码评审过程中,其他人可以理解你的测试,并评判你的测试是否足够充分
e.g:
Junit测试用例写法
使用@Test表明测试一个方法
使用assertEquals()测试方法是否成功实现
Junit中的assertXXX()方法
***需要对每一个编写的方法编写测试用例
测试覆盖度
***笛卡尔积:全覆盖
多个划分维度上的多个取值,要组合起来,每个组合都要有一个用例
***覆盖每个取值:最少1次即可
每个维度的每个取值至少被1个测试用例覆盖一次即可
***笛卡尔积VS覆盖每个取值
前者:测试完备,但用例数量多,测试代价高
后者:测试用例少,代价低,但测试覆盖度未必高
***代码覆盖度:已有的测试用例有多大程度覆盖了被测程序
代码覆盖度越低,测试越不充分但要做到很高的代码覆盖度,需要更多的测试用例,测试代价高
***分类:函数覆盖 + 语句覆盖 +分支覆盖 + 条件覆盖 + 路径覆盖
测试效果:路径覆盖>分支覆盖>语句覆盖
测试难度:路径覆盖>分支覆盖>语句覆盖
路径数量巨大,难以全覆盖
实际当中,根据预先设定的覆盖度标准,逐步增加测试用例的数量,直到覆盖度达到标准(例如语句覆盖100%、路径覆盖90%)。
***工具:EclEmma