(注:本文思想主要来源于哈工大计算学部王忠杰教授的《软件构造》)
一、 软件测试
软件测试是一项调查,目的是向涉众提供有关被测产品或服务质量的信息。也就是说,软件测试是提高软件质量的重要手段。它是执行程序或应用程序的过程,目的是寻找错误(错误或其他缺陷),并验证软件产品是否适合使用。也涉及到通过执行软件组件来评估一个或多个令人感兴趣的属性。
测试不同于调试。测试是发现错误,而调试是真正地去改正错误。
再好的软件开发者也无法保证百分百的正确率。测试的目标与其他开发活动的目标背道而驰,目标是找到错误。但是,测试永远不能完全证明没有错误。一个好的测试兼具能发现错误,不太复杂也不太简单,不冗余的这些特点。软件测试可以有以下常见的类型划分:
静态测试和动态测试的区别用通俗的话来讲,就是用放大镜看一个计划和真正执行一个计划的区别。
静态测试在不实际执行程序的情况下执行。静态测试通常是隐式的,如校对,加上编程工具/文本编辑器检查源代码结构或编译器(预编译器)检查语法和数据流作为静态程序分析。评审、演练或检查均被称为静态测试。
动态测试描述了对代码动态行为的测试,它实际上是用一组给定的测试用例执行已编程的代码。动态测试可以在程序100%完成之前开始,以便测试特定的代码部分,并应用于离散的功能或模块。其典型的技术是要么使用存根/驱动程序,要么在调试器环境中执行。
白盒测试通过查看源代码来测试程序的内部结构或工作方式。黑盒测试将软件视为一个“黑盒”,在不了解内部实现、不查看源代码的情况下检查功能。
为什么软件测试有难度?详尽的测试是不可行的:可能的测试用例的空间通常太大,无法详尽地覆盖。而随意的测试(“只是尝试一下,看看它是否有效”)不太可能发现错误,除非程序如此错误,以至于任意选择的输入更有可能失败而不是成功。随机或统计测试并不适用于软件。尽管其他工程学科可以测试小的随机样本(例如生产的硬盘的1%),并推断整个生产批次的不良率,但软件和这些产品有很大差异。另外,软件行为在可能输入的空间中不连续和离散地变化。堆栈溢出、内存不足错误和许多错误往往会突然发生,并且总是以相同的方式发生,而不是以概率变化发生。这给软件测试依据的选取造成了很多麻烦。也就是这些,让软件的测试充满了不规律的地方。
学着对你的代码暴力些,用“爱之深,责之切”的态度找它的错误。这才是一个程序员应具备的素养。
二、 软件测试
测试用例是一组测试输入、执行条件和预期结果。例如,测试用例={测试输入+执行条件+预期结果}。测试用例是为特定的目标而开发的,例如执行特定的程序路径或验证与特定需求的遵从性。换句话说,测试用例可以是您向程序提出的一个问题。运行测试的目的是获取信息,例如程序是否通过测试。
三、测试优先的编程
请在编写代码之前先编写测试!不要把测试留到最后,当你有一大堆未经验证的代码时。把测试留到最后只会让调试变得更长、更痛苦,因为bug可能在代码中的任何地方。而且,试着编写测试可以在你浪费时间编写一个有bug的规约(方法的规约是对方法行为的描述。包括参数的类型、返回值的类型、约束和它们之间的关系)。实现之前,尽早发现这些问题。
例如,测试驱动开发(TDD)是一种依赖于非常短的开发周期的重复的开发过程:将需求转化为非常具体的测试用例,然后对软件进行改进以通过新的测试。
三、单元测试
单元测试将验证工作集中在软件设计的最小单元——软件组件或模块上。单独测试模块可以大大简化调试。当模块的单元测试失败时,您可以更确信错误是在该模块中发现的,而不是在程序的任何地方。同时,对于设计信息的回顾为建立可能发现错误的测试用例提供了指导。每个测试用例都应该与一组预期的结果相结合。
因为组件不是一个独立的程序,所以必须经常为每个单元测试开发驱动程序和/或存根软件。驱动程序是一个“主程序”,它接受测试用例数据,将这些数据传递给组件(待测试),并打印相关的结果。存根用来替换从属于被测试组件的模块(由组件调用),存根使用从属模块的接口,可以进行最少的数据操作,打印条目验证,并将控制权返回给正在进行测试的模块。
四、使用JUnit进行自动化单元测试
JUnit是一个被广泛采用的Java单元测试框架。 单元测试方法通常包含对被测试模块的一个或多个调用,然后使用断言方法(如assertEquals、assertTrue和assertFalse)检查结果。
/**
* Tests calculatePolygonSidesFromAngle.
*/
@Test
public void calculatePolygonSidesFromAngleTest() {
assertEquals(3, TurtleSoup.calculatePolygonSidesFromAngle(60.0));//期望值为3 后面为实际值
assertEquals(7, TurtleSoup.calculatePolygonSidesFromAngle(128.57));
assertEquals(5, TurtleSoup.calculatePolygonSidesFromAngle(108.0));
}
/**
* Tests calculateBearingToPoint.
*/
@Test
public void calculateBearingToPointTest() {
assertEquals(0.0, TurtleSoup.calculateBearingToPoint(0.0, 0, 0, 0, 1), 0.001);
assertEquals(90.0, TurtleSoup.calculateBearingToPoint(0.0, 0, 0, 1, 0), 0.001);
assertEquals(359.0, TurtleSoup.calculateBearingToPoint(1.0, 4, 5, 4, 6), 0.001);
}
更多断言方法请点击以下链接查阅。
五、通过划分选择测试用例
等效划分是一种测试方法,它将程序的输入域划分为可以从中导出测试用例的等价类, 每个等价类代表着对输入约束加以满足/违反的有效/无效数据的集合,再从等价类中导出测试用例。
基于的假设:相似的输入,将会展示相似的行为。故可从每个等价类中选一个代表作为测试用例即可。这种方法通过选择不同的测试用例,并迫使测试探索随机测试可能无法到达的部分输入空间,从而最好地利用有限的测试资源,也降低了测试用例数量。
等价类可以根据以下的指南定义。1.如果输入条件指定了一个范围,则定义一个有效的和两个无效的等价类。2.如果输入条件需要特定的值,则定义一个有效等价类和一个无效等价类。3.如果输入条件指定了集合的成员,则定义一个有效等价类和一个无效等价类。4.如果输入条件是布尔值,则定义一个有效类和一个无效类。
如:输入的学号no需满足的条件:
•长度为10位:10、>10、<10
•以118开头:以此开头、以其他开头
•之后两位数应为03/36/37:03、36、37、其他
或者对于BigInteger对象的乘法。BigInteger是Java库中内置的一个类,它可以表示任何大小的整数,不像基本类型int和long只有有限的范围。在考虑其输入输出的特殊情况(-1,0,1)以及输入输出的上限(绝对值更大一些的情况),得到以下二维平面上的划分。
六、在划分中包含边界
更多的错误发生在输入域的边界,而不是在“中心”。比如集合的第一个和最后一个元素。边界值分析(Boundary Value Analysis, BVA)是一种基于边界值的测试用例选择方法。
七、代码覆盖度
代码覆盖率是一种度量,用于描述在特定测试套件运行时程序源代码执行的程度。 具有高代码覆盖率的程序,在测试期间执行了更多的源代码,这表明与具有低代码覆盖率的程序相比,它包含未检测到的软件错误的机会更低。
八、自动化测试和回归测试
手工测试的代价太高,最好达到完全的自动化。自动化测试意味着自动运行测试并检查测试结果。一个好的测试框架,比如JUnit,可以帮助您构建自动化的测试套件。请注意,像JUnit这样的自动化测试框架使运行测试变得容易,但是您仍然必须自己想出好的测试用例。自动测试生成是一个难题,仍然是计算机科学研究的活跃课题。
一旦实现了测试自动化,在修改代码时重新运行测试就非常重要了。在每次更改后运行所有测试称为回归测试。当bug出现时,立即为它编写一个引出它的测试用例,并立即将其添加到您的测试套件中。一旦你发现并修复了错误,你所有的测试用例都将通过,你将完成调试并对该错误进行回归测试。在实践中,自动化测试和回归测试几乎总是结合使用的。
九、记录测试策略
单元测试策略是ADT设计的补充文档。当他人进行代码审查,以检查您的测试是否足够时,好的策略使其他开发人员理解您的测试。