复习目录
Software Testing 软件测试
软件测试是什么
软件测试是提高软件质量的重要手段,通过软件测试,来确认软件是否达到可用级别(用户需求),软件测试关注系统的某一侧面的质量特性。
即使是最好的测试,也无法达到100%的无错误
下图为一些典型的残留缺陷率(kloc:1000lines of code)
测试跟其他活动的目标相反:破坏、证错、“负能量
再好的测试也无法证明系统里不存在错误
一个好的测试应该具有以下特点
- 能发现错误
- 不冗余
- 具有最佳特性(best of breed)
- 别太复杂也别太简单
软件测试的分类
测试可以为分
- 单元测试: 测试代码中特定节的方法(function)
- 集成测试: 测试多个类,包,组件,子系统组成的程序
- 系统测试: 测试完全集成的系统是否满足需求
- 回归测试: 程序出现bug后,重新测试
也可以分为
- 静态测试 Static testing
静态测试通常为编程工具检查源程序结构,编译器检查语法,数据流等等
Reviews, walkthroughs, or inspections are referred to as static testing. - 动态测试 Dynamic testing、
动态测试通常测试程序的动态行为,也就是程序在特定测试用例下的运行情况,可以在程序完成之前就可以开始对特定节的动态测试。
Typical techniques for this are either using stubs/drivers or execution from a debugger environment.
Testing vs. Debugging
测试Testing是在发现错误,而调试Debugging是更正错误
通过测试发现错误,调试消除错误,再次测试来确保程序没有错误
图示如下
Test Case 测试用例
test case = {test inputs + execution conditions+ expected results}
测试用例:输入+执行条件+期望结果
好的测试用例所具有的特点类似于好的测试
- 最可能发现错误
- 不重复不冗余
- 最有效
- 既不简单也不复杂
Test-First Programming 测试优先的编程
也即 在写代码之前先写好测试
Write the tests before you write the code.
这样的编程模式过程通常为
- 先写spec(specification 规约)
- 依据spec,写符合规约的测试用例
- 写代码、执行测试、有问题再改、再执行测试用例,直到通过它
好处是可以通过写测试用例,理解、修正、完善spec设计。也可以较早的发现spec中可能存在的漏洞。可以节省大量的调试时间
Unit Testing and Automated Unit Testing with Junit 单元测试以及Junit
单元测试: 针对软件的最小单元模型开展测试,隔离各个模块,容易定位错误和调试
可以通过Junit来对程序进行测试
Junit 官网:Junit
Junit 百度百科:Junit_百度百科
一个Junit unit test 以@Test声明开始,通过assertion 方法来测试
下图为示例,测试Math.max()
Black-box Testing
黑盒测试 用于检查代码的功能,不关心内部实现细节检查程序是否符合规约,用尽可能少的测试用例,尽快运行,并尽可能大的发现程序的错误、
1)Choosing Test Cases by Partitioning
Equivalence Partitioning 等价类划分
基于等价类划分的测试:将被测函数的输入域划分为等价类, 从等价类中导出测试用例,针对每个输入数据需要满足的约束条件,划分等价类
等价类需要满足对称、传递、自反的性质
每个等价类代表着对输入约束加以满足/违反的有效 /无效数据的集合
基于的假设:相似的输入,将会展示相似的行为。故可从每个等价类中选一个代表作为 测试用例,从而降低测试用例数量。
如:
- 输入数据限定了数值范围,则有一个有效等价类,两个无效等价类;
- 输入数据指明了特定的值,则有一个有效等价类,一个无效等价类
- 输入数据确定了一组数值,则有一个有效等价类,一个无效等价类
- 输入数据是布尔类型,则有一个有效等价类,一个无效等价类
示例如下
BigInteger.multiply() 该方法返回a*b的值,a和b的值可以是任意的整数
a的等价类可划分为: 0,1,-1,小的正整数,大的正整数,小的负整数,大的负整数,b也如此,
故整个测试可划分为 7*7=49个等价类。
2)Include Boundaries in the Partition
大量的错误发生在输入域的边界而非中央,这其中的原因是
- 程序员经常犯一些大小差1的错误
- 某些边界值是“特殊情况”,需要特殊处理
- 程序的行为在边界的地方可能发生 “突变”
边界值分析方法是对等价类划分方法的补充,在等价类划分时,将边界作为等价类之一加入考虑
例如,某个方法的参数为n
设计5个参数用例 n的最小值,略高于最小值,正常值,略低于最大值,最大值
还可以增加 略高于最大值和略低于最小值
如果n介于18-56
则可选取17 18 19 55 56 57
两种极端等价类的划分
- 笛卡尔积:全覆盖 多个划分维度上的多个取值,要组合起来,每个组合都要有一个用例,不可能出现的情况不用考虑
- 每个维度的每个取值至少被1个测试用例覆盖一 次即可
如max() max : int × int → int.
如下图的例子
- 笛卡尔积 共包含 3*5*5 = 75种情况(包含不可能出现的情况)
- 覆盖每个取值:最少1次即可 则最少只需要5种测试用例即可
{a, b} : {0, 0} {-1, 1} {1, -1} {min, max} { max, min}
笛卡尔积:测试完备,但用例数量多,测试代价高
覆盖每个取值:测试用例少,代价低,但测试覆盖度未必高。
White-box Testing
黑盒测试完全从函数spec导出测试用例,不考虑函数内部实现,而白盒测试要考虑内部实现细节根据程序执行路径设计测试用例。
独立/基本路径测试 对程序所有执行路径进行等价类划分,找出有代表性的最简单的路径(例如循环只需执行1次),设计测试用例使每一条基本路径被至少覆盖1次
Coverage of Testing
代码覆盖度 已有的测试用例有多大程度覆盖了被测程序, 代码覆盖度越低,测试越不充分 但要做到很高的代码覆盖度,需要更多的测试用例,测试代价高
覆盖度的种类
- 函数覆盖 has each function in program been called?
- 语句覆盖 is every statement run by some test case?
- 分支覆盖 for every if or while or switch-case or for statement in the program, are both the true and the false direction taken by some test case?
- 条件覆盖 for every condition in if/while/for/switch-case statement, are both the true/false direction taken by some test case?
- 路径覆盖 is every possible combination of branches — every path
through the program — taken by some test case?
测试效果:路径覆盖>分支覆盖>语句覆盖
测试难度:路径覆盖>分支覆盖>语句覆盖
Automated Testing and Regression Testing
Automated Testing
手工测试的代价太高,最好达到完全的自动化,自动调用被测函数、自动判定测试结果、自动计算覆盖度
不同于Automatic test generation
Regression testing
回归测试:一旦程序被修改,重 新执行之前的所有测试
一旦发现bug,要马上写一个可重现该bug的测试用例,并将其加入测试库
Documenting Your Testing Strategy
测试策略(根据什么来选择测试用例)非常重要,需要在程 序中显式记录下来
目的:在代码评审过程中,其他人可以理解你的测试,并评判你的测试是否足够充分