关于软件构造测试的个人理解
作者:互联网
上课时,老师最常说的一句话就是“测试优于编程”,体现了测试的重要性接下来让我们了解一下测试以及它的重要性吧。
一、何为测试
在我看来测试就是寻找我们编写的程序中寻找bug的一种尝试。毕竟没有人可以写出十分完美的程序,不过怎样程序的本身都会有bug,所以这需要我们不断地进行调试:
首先是进行形式推理,看看自己的思想是否有误,要保证逻辑的正确才可以保证后面代码的正确性,形式推理目前还缺乏自动化的工具,通常需要漫长的手工计算。即使是这样,一些关键性的小程序也是需要被证明的,例如操作系统的调度程序、虚拟机里的字节码解释器,或者是 文件系统.
其次就是代码的审查,自己写的代码可以自己首先寻找一下是否有误,如果找不出来,可以让别人帮着看一下是否有逻辑上或者语法上的错误,这需要我们在编写程序时对主要代码进行详细的注释。当然在目前编写代码的程序可以自动的识别出一些简单的输入错误或者类型错误并给出解决方案,但是有一些错误还是要我们自己寻找出来进行修改的。
最后就到了测试:即选择合适的输入输出用例,通过运行程序检查程序的问题。即使是最优的验证,程序也不可能达到十全十美,这里列出了一般情况下程序的剩余缺陷率residual defect rates(软件发行时存在的bug比率) ,这里的单位是每 kloc (每一千行代码):
- 1 - 10 defects/kloc: 常见的工业级软件。
- 0.1 - 1 defects/kloc: 高质量验证后的软件。例如Java的官方库可能就是这个级别。
- 0.01 - 0.1 defects/kloc: 最高级别、军工/安全关键软件。例如NASA或者像Praxis这样的公司
这看起来让人沮丧,想一想,如果你写了100万行的大型程序,那你很可能没检查出1000个bug!
二、测试
1.测试是非常的困难的,因为对一个大型的程序或者系统来说,我们难以把每种情况都列举出来,有时候也是没有必要的。对于测试一般要满足一下几个要求:
correctness 是否满足需求spec
correctness,robustness 是否正确的响应所有可能的需求
correctness 性能是否可接受
correctness, robustness 是否可用
correctness 可否正确的安装部署和运行
correctness 达到期望
2.测试的目标是:1.找出程序中的错误;2.优化程序,使程序既不简单也不复杂;3.具有最佳特性
3.测试的层次(根据老师上课讲的)
单元测试:测试,验证一个特定的代码段的功能,通常在函数水平
集成测试:由多个程序员或编程团队创建的两个或两个以上的类,包,组件,系统的联合执行。
系统测试:测试一个完全集成的系统,验证系统满足其要求,在最终的配置上执行软件。
4.测试的分类
静态测试:在不实际执行程序的情况下执行的,通常是隐式的,比如校对,当编程工具/文本编辑器检查源代码结构时,检查语法和数据流作为静态程序分析。
动态测试:描述的是对代码动态行为的测试,实际上实在给定的测试集上运行程序
白盒测试:对程序内部代码结构的测试
黑盒测试:对程序外部表现出来的行为的测试
回归测试:指修改了旧代码后,重新进行测试以确认修改没有引入新的错误或导致其他代码产生错误
验收测试:是部署软件之前的最后一个测试操作。在软件产品完成了单元测试、集成测试和系统测试之后,产品发布之前所进行的软件测试活动。
5.测试的等价类
基于等价类划分的测试:将被测函数的输入域划分为等价类, 从等价类中导出测试用例
针对每个输 入数据需要满足的约束条件,划分等价类
每个等价类代表着对输入约束加以满足/违反的有效 /无效数据的集合 。
而相似的输入,将会展示相似的行为。
故可从每个等价类中选一个代表作为 测试用例即可
可以按照下面的准则划分:
输入数据限定了数值范围
输入数据指明了特定的值
输入数据确定了一组数
输入数据是Y/N
三、测试举例
测试需要一个良好的心态:当你在写一个程序的时候,你的心态一定是让这个程序正常运行,但是作为一个测试者,你应该想方设法让程序崩溃。我们要区分好自己使编写者还是测试者的身份。
测试开始应该尽量早,并且要频繁地测试。而不是一开始不测试,等到最后把程序写完才开始测试,把测试工作留到最后只会让调试的时间更久并且调试过程更加痛苦,因为你的代码将会充斥着bug。反之,如果你在编码的过程中就进行测试,情况就会好的多。所以,我们要养成随写随测,随写随debug的良好习惯。
1.设计测试的准则:
代表性:测试案例可以代表和涵盖各种合理的、不合理的、合法的、非法的、边界和跨国界的、极端的输入数据、操作和环境设置等
决定性:测试结果的正确性是确定的,每个测试用例应该有相应的预期结果。
可重复性:对于相同的测试用例,系统实现的结果应该是相同的。
2.在测试优先编程中,测试程序先于代码完成。编写一个函数应该按如下步骤进行:
- 为函数写一个规格说明。
- 为上一步的规格说明写一些测试用例。
- 编写实际代码。一旦你的代码通过了所有你写的测试用例,这个函数就算完成了。
规格说明描述了这个函数的输入输出行为。它确定了函数参数的类型和对它们的所有约束(例如exp函数的参数必须是大于0的)。它还定义了函数的返回值类型以及返回值和输入之间的关系。在代码中,规格说明包括了函数签名和一些描述函数功能的注释。
先完成测试用例的编写能够让你更好地理解规格说明。规格说明也可能存在问题——不正确、不完整、模棱两可、缺失边界情况。先尝试编写测试用例,可以在你浪费时间实现一个有问题的规格说明之前发现这些问题。
例1.BigInteger.multiply
函数的功能:BigInteger X BigInteger --> BigInteger
函数输入: 二维空间中的一个点
等价类划分:
考虑输入数据的正负:
两个都是正数,两个都是负数,一个正数一个负数
考虑输入数据的特殊情况:
0, 1, -1
考虑输入的上下限
作为一个合格的测试员,我们还要想一想BigInteger的乘法可能是怎么运算的:它可能在输入数据绝对值较小时使用 int
或 long
,这样运算起来快一些,只有当数据很大时才会使用更费劲的存储方法(例如列表)。所以我们也应该将对数据的大小进行分区:
- a或b较小
- a或b的绝对值大于
Long.MAX_VALUE
,即Java原始整型的最大值,大约是2^63。
最终结果
3.两个覆盖划分的方法
全覆盖:在多个划分维度上的多个取值,要组合起来,每个组合都要有一个用例。测试完备,但测试用例多,测试代价高
部分覆盖:每个维度的每个取值至少被一个测试用例覆盖一次即可,测试用例少,代价地,但测试覆盖度未必高
四、JUnit的使用
JUnit是一个Java语言的单元测试框架。JUnit是由 Erich Gamma 和 Kent Beck 编写的一个回归测试框架(regression testing framework)。Junit测试是程序员测试,即所谓白盒测试,因为程序员知道被测试的软件如何(How)完成功能和完成什么样(What)的功能。Junit是一套框架,继承TestCase类,就可以用Junit进行自动测试了。
最简单的范例如下:
1、创建一个TestCase的子类
package junitfaq;
import java.util.*;
import junit.framework.*;
public class SimpleTest extends TestCase {
public SimpleTest(String name) {
super(name);
}
2、写一个测试方法断言期望的结果
public void testEmptyCollection() {
Collection collection = new ArrayList();
assertTrue(collection.isEmpty());
}
注意:JUnit推荐的做法是以test作为待测试的方法的开头,这样这些方法可以被自动找到并被测试。
3、写一个suite()方法,它会使用反射动态的创建一个包含所有的testXxxx方法的测试套件
public static Test suite() {
return new TestSuite(SimpleTest.class);
}
4、写一个main()方法以文本运行器的方式方便的运行测试
public static void main(String args[]) {
junit.textui.TestRunner.run(suite());
}
}
5、运行测试
以文本方式运行:
java junitfaq.SimpleTest
通过的测试结果是:
.
Time: 0
OK (1 tests)
Time上的小点表示测试个数,如果测试通过则显示OK。否则在小点的后边标上F,表示该测试失败。
每次的测试结果都应该是OK的,这样才能说明测试是成功的,如果不成功就要马上根据提示信息进行修正了。
如果JUnit报告了测试没有成功,它会区分失败(failures)和错误(errors)。失败是你的代码中的assert方法失败引起的;而错误则是代码异常引起的,例如ArrayIndexOutOfBoundsException。
以图形方式运行:
java junit.swingui.TestRunner junitfaq.SimpleTest
通过的测试结果在图形界面的绿色条部分。
以上是最简单的测试样例,在实际的测试中我们测试某个类的功能是常常需要执行一些共同的操作,完成以后需要销毁所占用的资源(例如网络连接、数据库连接,关闭打开的文件等),TestCase类给我们提供了setUp方法和tearDown方法,setUp方法的内容在测试你编写的TestCase子类的每个testXxxx方法之前都会运行,而tearDown方法的内容在每个testXxxx方法结束以后都会执行。这个既共享了初始化代码,又消除了各个测试代码之间可能产生的相互影响。
我们常用的注解:
1.@Before 注解:与junit3.x中的setUp()方法功能一样,在每个测试方法之前执行;
2.@After 注解:与junit3.x中的tearDown()方法功能一样,在每个测试方法之后执行;
3.@BeforeClass 注解:在所有方法执行之前执行;
4.@AfterClass 注解:在所有方法执行之后执行;
5.@Test(timeout = xxx) 注解:设置当前测试方法在一定时间内运行完,否则返回错误;
6.@Test(expected = Exception.class) 注解:设置被测试的方法是否有异常抛出。抛出异常类型为:Exception.class;
7.@Ignore 注解:注释掉一个测试方法或一个类,被注释的方法或类,不会被执行。
五、总结
在上面的叙述中,我们主要讲述了:
测试优先编程——在写代码前先写好测试用例,尽早发现bug。
利用分区与分区边界来选择测试用例。
JUnit的使用
好软件具备的三个属性:
远离bug 测试的意义在于发现程序中的bug,而“测试优先编程”的价值在于尽可能早的发现这些bug。
易读性 额.......测试并不会使代码审查变得容易,但是我们也要注意正确书写测试注释。
可改动性 我们针对改动后的程序进行测试时只需要依赖规格说明中的行为描述。另外,当我们完成修改后,自动化回归测试能够帮助我们杜绝新的bug产生。
标签:关于软件,代码,程序,构造,测试用例,测试,bug,输入 来源: https://blog.csdn.net/sbj001/article/details/117232656