目录
本次课程的目标
1.理解测试的意义,并了解“测试优先编程”的过程
2.能够使用“分区”的方法选择合适的输入输出测试用例
3.能够通过代码覆盖率来评价一个测试的好坏
4.理解黑盒/白盒测试、单元/集成测试、自动化回归测试。
测试
“测试”是“验证”的一种例子。而验证的目的就是发现程序中的问题,以此提升你对程序正确性的信心。验证包括:
1、形式推理,即通过理论推理证明程序的正确性。形式推理目前还缺乏自动化的工具,通常需要漫长的手工计算。即使是这样,一些关键性的小程序也是需要被证明的,例如操作系统的调度程序、虚拟机里的字节码解释器,或者是 文件系统.
2、代码审查. 即让别人仔细的阅读、审校、评价你的代码,这也是发现bug的一个常用方法,我们会在下一个reading里面介绍这种方法。
3、测试.即选择合适的输入输出用例,通过运行程序检查程序的问题。
为什么软件测试很困难
这里有一些在工业界测试产品常用的方法,可是它们在软件行业无法发挥应有的作用。
1、尽力测试:这通常是不可行的,因为大多数情况下输入空间会非常大,例如仅仅是一个浮点数乘法a*b ,其总共的取值就有2^64种可能性!
2、随机测试 : 这通常难以发现bug,除非这个程序到处都是bug以至于随便一个输入都能崩溃。即使我们修复了测试出来的bug,随机的输入也不能使我们对程序的正确性很确定。
3、基于统计方法的测试:遗憾的是,这种方法对软件不那么奏效。在物理系统里,工程师可以通过特定的方法加速实验的进程。软件的行为通常是离散且不可预测的。程序可能在上一秒还完全正常的工作,突然就崩溃了,也可能对于大多数输入都没问题,对于一个值就崩溃了。
综上,我们必须系统而且小心的选择测试用例。
测试优先编程(Test-first Programming)
测试开始的时间应该尽量早,并且要频繁地测试。不要把测试工作留到最后。把测试工作留到最后只会让调试的时间更久并且调试过程更加痛苦,因为你的代码将会充斥着bug。反之,如果你在编码的过程中就进行测试,情况就会好的多。
在测试优先编程中,测试程序先于代码完成。编写一个函数应该按如下步骤进行:
1、为函数写一个规格说明。
2、为上一步的规格说明写一些测试用例。
3、编写实际代码。一旦你的代码通过了所有你写的测试用例,这个函数就算完成了。
规格说明描述了这个函数的输入输出行为。它确定了函数参数的类型和对它们的所有约束
测试等级以及层次
单元测试单元测试:是指验证特定代码部分的功能,通常在功能级别。
集成测试集成测试:两个或多个的组合执行已创建的类,包,组件,子系统多个程序员或编程团队。
系统测试系统测试:测试一个完全集成的系,以验证系统满足其要求,然后执行最终配置的软件。
黑盒测试vs白盒测试
白盒测试可测试内部结构或通过查看源代码。
黑匣子测试将软件视为“黑匣子”,功能,而无需任何内部实现知识,没有看到源代码。
tips:
在做白盒测试时。你必须注意:你的测试用例不需要尝试规格说明中没有明确要求的实现行为。例如,如果规格说明中说“如果输入没有格式化,那么将抛出异常”,那么你不应该特地的检查程序是否抛出NullPointerExpection异常,因为当前的代码实现决定了程序有可能抛出这个异常。在这种情况下,规格说明允许任何异常被抛出,所以你的测试用例同样应该“宽容”地保留实现者的自由。我们将会在这门课接下来的课时中讨论更多关于规格说明的问题。
测试用例的选择
一个好的测试用例:
Most likely to catch the wrong 最可能发现错误
Not repetitive and not redundant 不重复、不冗余
The most effective in a group of similar test cases 最有效
Neither too simple nor too complicated 既不简单也不复杂
选择合适的测试用例是一个具有挑战性的问题。我们即希望测试空间足够小,以便能够快速完成测试,又希望测试用例能够验证尽可能多的情况。
等价类的划分
为了达到这个目的,我们可以先将输入空间划分为几个子域 ,每一个子域都是一类相似的数据。如上图所示,我们在每个子域中选取一些数据,它们合并起来就是我们需要的输入用例。
分区背后的原理在于同一类型的数据在程序中的行为大多类似,所以我们可以用一小部分代表整体的行为。这个方法的优点在于强迫程序相应输入空间里的不同地方,有效的利用了测试资源。
如果我们要确保测试的输出能够覆盖输出空间的不同地方,也可以将输出空间划分为几个子域(哪些输出代表程序发生了相似的行为)。大多数情况下,对输入分区就足够了。
基于的假设:相似的输入,将会展示相似的行为。故可从每个等价类中选一个代表作为测试用例即可。
通过选择以下方法,该方法可以充分利用有限的测试资源不同的测试用例,并迫使测试探索部分输入随机测试可能无法达到的空间。
一个例子

覆盖分区的两个极限情况
在分区后,我们可以选择“尽力(how exhaustive we want)”的程度来测试我们的分区,这里有两个极限情况:
1、完全笛卡尔乘积
即对每一个存在组合都进行测试。例如在第一个例子multiply中,我们一共使用了 7 × 7 = 49 个测试用例,每一个组合都用上了。对于第二个例子,就会是 3 × 5 × 5 = 75个测试用例。要注意的是,实际上有一些组合是不存在的,例如 a < b, a=0, b=0。
2、每一个分区被覆盖即可
即每一个分区至少被覆盖一次。例如我们在第二个例子max中只使用了5个测试用例,但是这5个用例覆盖到了我们的三维输入空间的所有分区。
在实际测试中我们通常在这两个极限中折中,这种折中是基于人们的经验,对代码的获取度(黑白盒测试)、以及对代码的覆盖率。
用JUnit做自动化单元测试
一个良好的测试程序应该测试软件的每一个模块(方法或者类)。如果这种测试每次是对一个孤立的模块单独进行的,那么这就称为“单元测试”。单元测试的好处在于debug,如果你发现一个单元测试失败了,那么bug很可能就在这个单元内部,而不是软件的其他地方。
JUnit 是Java中一个被广泛只用的测试库,我们在以后的课程中也会大量使用它。一个JUnit测试单元是以一个方法(method)写出的,其首部有一个 @Test声明。一个测试单元通常含有对测试的模块进行的一次或多次调用,同时会用断言检查模块的返回值,比如 assertEquals, assertTrue, 和 assertFalse.
如果一个测试断言失败了,它会立即返回,JUnit也会记录下这次测试的失败。一个测试类可以有很多 @Test 方法,它们可以各自独立的进行测试,即使有一个失败了,其它的测试也会继续进行。
测试策略
例子
/*
* Testing strategy
*
* Partition the inputs as follows:
* text.length(): 0, 1, > 1
* start: 0, 1, 1 < start < text.length(),
* text.length() - 1, text.length()
* text.length()-start: 0, 1, even > 1, odd > 1
*
* Include even- and odd-length reversals because
* only odd has a middle element that doesn't move.
*
* Exhaustive Cartesian coverage of partitions.
*/
另外,每一个测试方法都要有一个小的注解,告诉读者这个测试方法是代表我们测试策略中的哪一部分,例如:
// covers test.length() = 0,
// start = 0 = text.length(),
// text.length()-start = 0
@Test public void testEmpty() {
assertEquals("", reverseEnd("", 0));
}
覆盖率
一种判断测试的好坏的方法就是看该测试对软件的测试程度。这种测试程度也称为“覆盖率”。以下是常见的三种覆盖率:
1、语句覆盖率: 每一个语句都被测试到了吗
2、分支覆盖率:对于每一个if 或 while 等等控制操作,它们的分支都被测试过吗?
3、路径覆盖率: 每一种分支的组合路径都被测试过吗?
测试效果:路径覆盖>分支覆盖>语句覆盖
测试难度:路径覆盖>分支覆盖>语句覆盖
A good code coverage tool for Eclipse is EclEmma(Eclipse一般自动封装的插件)
自动化测试和回归测试
手工测试的代价太高,最好达到完全的自动化
自动调用被测函数、自动判定测试结果、自动计算覆盖度
1、测试驱动程序不应是交互式程序,它会提示您输入信息并打印出结果以供您手动检查。
2、相反,测试驱动程序应在固定的测试用例上调用模块本身,并自动检查结果是否正确。
3、测试驱动程序的结果应为“所有测试都正常”或“这些测试失败:……”
回归测试:一旦程序被修改,重新执行之前的所有测试
总结
测试优先编程——在写代码前先写好测试用例,尽早发现bug。
利用分区与分区边界来选择测试用例。
白盒测试与声明覆盖率。
单元测试——将测试模块隔离开来。
自动化回归测试杜绝新的bug产生。
本文深入讲解软件测试的重要性和策略,涵盖测试优先编程、测试等级、黑盒与白盒测试、测试用例选择及自动化测试等内容,帮助开发者提高软件质量。

3656

被折叠的 条评论
为什么被折叠?



