软件构造课程随笔——【测试优先编程】

本文深入讲解软件测试的重要性和策略,涵盖测试优先编程、测试等级、黑盒与白盒测试、测试用例选择及自动化测试等内容,帮助开发者提高软件质量。

本次课程的目标

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产生。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值