单元测试之道Java版------使用JUnit------
第1章 序言
·什么是单元测试
单元测试是开发者编写的一小段代码,用于检验被测代码的一个很小的、很明确的功能是否正确。通常而言,一个单元测试是用于判断某个特定条件(或者场景)下某个特定函数的行为。
·为什么要使用单元测试
单元测试不但会使你的工作完成得更轻松,而且会令你的设计变得更好。甚至大大减少你花在调试上面的时间。
·不写测试的借口
编写单元测试太花时间了
运行测试的时间太长了
测试代码并不是我的工作
我并不清楚代码的行为,所以也就无从测试
但是这些代码都能够编译通过
公司请我来试我是为了写代码,而不是写测试
如果我让测试员或者QA人员没有工作,那么我会觉得很内疚
我的公司并不会让我在真实系统中运行单元测试
第3章 使用JUnit编写测试
3.1 构建单元测试
测试代码必须要做以下几件事情:
·准备测试所需要的各种条件(创建所有必须的对象,分配必要的资源等等)。
·调用要测试的方法。
·验证被测试方法的行为和期望是否一致。
·完成后清理各种资源。
3.2 JUnit的各种断言
JUnit提供了一些辅助函数,用于帮助你确定某个被测试函数是否工作正常。通常而言,我们把所有这些函数统称为断言。断言是单元测试最基本的组成部分。
当一个失败或错误出现的时候,当前测试方法的执行流程将会被种植,但是(位于同一个测试类中的)其他测试将会继续运行。
· assertEquals
assertEquals([String
message],
expected,
actual)
这是使用得最多的断言形式。在上面的参数中,expected是你的期望值(通常都是硬编码的),actual是被测试代码实际产生的值,message是一个可选的消息,如果提供的话,将会在发生错误的时候报告这个消息。当然,你完全可以不提供这个message参数,而只提供expected和value这两个值。
任何对象都可以拿来做相等性测试:适当的相等性判断方法会被用来做这样的比较。值得注意的是使用原生数组的equals方法时,它并不是比较数组的内容,而只是比较数组引用本身,而这大概不是你希望的吧。
计算机并不能精确地表示所有的浮点数,通常都会有一些偏差。因而,如果你想用断言来比较浮点数(在Java中,是类型为float或者double的数),则需要制定一个额外的误差参数。它表明你需要多接近才能认为两数“相等”。
assertEquals([String
message],
expected,
actual,
tolerance)
·assertNull
assertNull([String
message], java.lang.Object object)
assertNotNull([String
message], java.lang.Object object)
验证一个给定的对象是否为null(或者为非null),如果答案为否,则将会失败。message参数是可选的。
·assertSame
assertSame([String
message], expected, actual)
验证expected参数和actual参数所引用的是否为同一个对象,如果不是的话,将会失败。message参数是可选的。
assertNotSame([String
message], expected, actual)
验证expected参数和actual参数所应用的是否为不同的对象,如果是相同的话,将会失败。message参数是可选的。
·assertTrue
assertTrue([String
message], Boolean condition)
验证给定的二元条件是否为真,如果为假的话,将会失败。meesage参数是可选的。
如果你发现测试代码像下面这样,宛如废话一般:
asserTrue(true);
那么你就该好好想想这些代码了。对于这种写法,除非是被用于确认某个分支,或者一场逻辑才有可能是正确的选择;否则的话,很可能就是一个糟糕的主意。
assertFalse([String
message], Boolean condition)
上面的代码用于验证给定的二元条件是否为假。如果不是的话,该测试将会失败。
·fail
fail([String
message])
上面的断言将会使测试立即失败,其中message参数是可选的。这种断言通常被用于标记某个不应该被到达的分支(譬如,在一个与预期发生的异常之后)。
·使用断言
一般而言,一个测试方法会包含有多个断言,因为你需要验证该方法的多个方面以及内在的多种联系。
当有测试失败的时候,无论如何都不能给原有代码再添加新的特性!此时你应该尽快地修复这个错误,直到让所有的测试都能顺利通过。
3.3 JUnit框架
每个包含测试的类都必须如所示那样由TestCase继承而来。基类TestCase提供了我们所需的大部分单元测试功能,包括所有在前面讲述过的断言方法。
基类需要一个以String为参数的构造函数,因而我们必须调用super以传递这么一个名字。
测试类包含了名为test…的方法。而所有以test开头的方法都会被JUnit自动运行。你还可以通过定义suite方法制定特殊的函数来运行。
3.4 JUnit测试的组成
一个测试类包含一些测试方法;每个方法包含一个或者多个断言语句。但是测试类也能调用其它测试类:单独的类、包、甚至完整的一个系统。可以通过创建test suite来取得。任何测试类都能包含一个名为suite的静态方法。
public static
Test suite();
你可以提供suite()方法来返回任何你想要的测试集合(没有suite()方法,JUnit会自动运行所有的test…方法)。但是你可能需要手工添加特殊的测试,包括其他suite。
· Per-method的Setup和Tear-down
JUnit的TestCase基类提供两个方法供你改写,分别用于环境的建立和清理:
protected void setup();
protected void teardown();
· Per-suite
Setup和Tear-down
一般而言,你只须针对每个方法设置运行环境;但是在某些情况下,你须为整个test suite设置一些环境,以及在test suite中的所有方法都执行完成后做一些清理工作。要达到这种效果,你需要per-suite setup和per-suite teardown。
Per-suite的setup要复杂一些。你需要提供所需测试的一个suite(无论通过什么样的方式)并且把它包装进一个TestSetup对象。
注意你可以在同一个类中同时使用per-sutie和per-test的setup()和teardown。
3.5 自定义JUnit断言
如果你有需要在整个项目中共享的断言或者公共代码,你也许需要考虑从TestCase继承一个类并且使用这个字类来进行所有的测试。
事实上,开始新项目时总是从自己的自定义基类继承而不直接从JUnit的类继承通常是一个好主意——即便你的基类在一开始没有添加任何额外的功能。这样做的好处是当你需要添加一个所有测试类都需要的方法或者能力时,可以简单地编辑你的基类而不需要改动项目中的所有test case。
3.6 JUnit和异常
对于测试而言,下面两种异常是我们可能会感兴趣的:
1. 从测试代码抛出的可预测异常。
2. 由于某个模块(或代码)发生严重错误,而抛出的不可预测异常。
任何对assertTrue(true)的使用都应该被翻译为“我预期控制流程会达到这个地方”。这对将来可能的误解来说会起到强有力的文档的作用。然而,不要忘记一个assertTrue(true)没有被调用不会产生任何错误的。
通常而言,对于方法中每个被期望的异常,你都应该写一个专门的测试来确认该方法在应该抛出异常的时候确实会抛出异常。
对于处于出乎意料的异常,你最好简单的改变你的测试方法的声明让它能抛出可能的异常。JUnit框架可以捕获任何异常,并且把它报告为一个错误,这些都不需要你的参与。
3.7 关于命名的更多说明
如果编写了一个测试,但是实现代码还没有准备好,可以将以“test”打头的测试方法名米功能为别的,譬如把“test”去掉,然后等准备好了要来运行测试的时候再改回来。
无论如何,你要避免养成忽略“失败的测试结果”的习惯。
3.8 JUnit测试骨架
用JUnit写测试真正所需要的就三件事:
1. 一个import语句引入所有junit.framework.*下的类。
2. 一个extends语句让你的类从TestCase继承。
3. 一个调用super(string)的构造函数。