1、为什么需要单元测试
软件开发的标准过程包括以下几个阶段:[需求分析阶段]、[设计阶段]、[实现阶段]、[测试阶段]、[发布]。其中测试阶段通过人工或者自动手段来运行或测试某个系统的过程,其目的在于检验它是否满足规定的需求或弄清预期结果与实际结果之间的差别。按照软件工程思想,软件测试可以分为单元测试、集成测试、功能测试、系统测试等。功能测试和系统测试一般来说是测试人员的职责,但单元测试和集成则必须由开发人员保证。
1)单元测试:单元测试时开发者编写的一小段代码,用于检验目标代码的一个很小的、很明确的功能是否正确。通常而言,一个单元测试用于判断某个特定条件或特定场景下某个特定函数的行为。在一个情况下,一个功能模块往往会调用其他功能模块完成某项功能,如业务层的业务类可能会调用多个DAO完成某项任务。对某个功能模块进行单元测试时,我们希望屏蔽对外功能模块的依赖,以便将焦点放在目标功能模块的测试上。这时模拟对象是最有力的工具,它依据外在模块的接口模拟特定操作行为,这样单元测试就可以在假设关联模块正确工作的情况下验证本模块逻辑的正确性了。
2)集成测试:集成测试是在功能模块开发完成以后,为验证功能模块之间匹配调用的正确性而进行的测试。在单元测试时,往往需要通过模拟对象屏蔽外在模块的依赖,而集成测试恰恰是要验证模块之间集成后的正确性。
3)功能测试:功能测试主要检查已实现的软件是否满足了需求规格说明中确定了的各种需求,以及软件功能是否完全、正确。
4)系统测试主要对已经经过确定的软件纳入实际运行环境中,与其他系统成份组合在一起进行测试。
2、测试的好处
1)是软件质量最简单、最有效的保证;
2)是目标代码最清晰、最有效的文档;
3)可以优化目标代码的设计;
4)是代码重构的保障;
5)是回归测试和持续集成的基石。
3、单元测试基本概念
1)被测系统:SUT(System Under Test)
被测系统表示正在被测试的系统,目的是测试系统能否正确操作。软件系统测试的一个特例是对应用软件的测试,称为被测应用程序(AUT,Application Under Test)。SUT也表明软件已经到了成熟期,因为系统测试在测试周期中是集成测试的后一阶段。
2)测试替身:Test Double
在单元测试时,使用Test Double减少对被测对象的依赖,使得测试更加单一。同时,让测试案例执行的时间更短,运行更加稳定,同时能对SUT内部的输入输出进行验证,让测试更加彻底深入。但是,Test Double也不是万能的,Test Double不能被过度使用,因为实际交付的产品是使用实际对象的,过度使用Test Double会让测试变得越来越脱离实际。
要理解测试替身,需要了解一下Dummy Object, Test Stub,Test Spy, Fake Object这几个概念。
a、Dummy Object:Dummy Object泛指在测试中必须传入的对象,而传入的这些对象实际上并不会产生任何作用,仅仅是为了能够调用被测对象儿必须传入的一个东西。
b、Test Stub:测试桩是用来接受SUT内部的间接输入(indirect inputs),并返回特定的值给SUT。可以理解Test Stub是在SUT内部打的一个桩,可以按照我们的要求返回特定的内容给SUT,Test Stub的交互完全在SUT内部,因此,它不会返回内容给测试案例,也不会对SUT内部的输入进行验证。
c、Test Spy:Test Spy像一个间谍,安插在了SUT内部,专门负责将SUT内部的间接输出(indirect outputs)传到外部。它的特点是将内部的间接输出返回给测试用例,由测试案例进行验证,Test Spy只负责获取内部情报,并把情报发出去,不负责验证情报的正确性。
d、Mock Object:Mock Object和Test Spy有类似的地方,它也是安插在SUT内部,获取到SUT内部的间接输出(indirect outputs),不同的是,Mock Object还负责对情报(intelligence)进行验证,总部(外部的测试案例)信任Mock Object的验证结果。
e、Fake Object:经常,我们会把Fake Object和Test Stub搞混,因为它们都和外部没有交互,对内部的输入输出也不进行验证。不同的是,Fake Object并不关注SUT内部的间接输入(indirect inputs)或间接输出(indirect outputs),它仅仅是用来替代一个实际的对象,并且拥有几乎和实际对象一样的功能,保证SUT能够正常工作。实际对象过分依赖外部环境,Fake Object可以减少这样的依赖。
3)测试夹具:Test Fixture
测试夹具(Fixture),就是测试运行程序(test runner)会在测试方法之前自动初始化、回收资源的工作。在Junit4中通过@Before来初始化字段和配置环境,通过@After来进行注销。这可以保证各个测试之间的独立性和互不干扰,但是效率低。
一个测试用例可以包含若干个打上@Test注解的测试方法,测试用例测试一个或多个类API接口的正确性,当然在调用类API时,需要事先创建这个类的对象及一些关联的对象,这组对象就称为测试夹具(Fixture),相当于测试用例的“工作对象”。
4)测试用例:Test Case
在JUnit3中,测试方法都必须以test为前缀,且必须时public void的,JUnit4以后,就没有这个限制,只要在每个测试方法标注@Test注解,方法签名可以是任何取名。可以在一个测试用例类中添加多个测试方法,运行器为每个方法生成一个测试用例实例并分别运行。
5)测试套件:Test Suite
通过TestSuite对象将多个测试用例组装成一个测试套件,则测试套件批量运行。需要特别指出的是,可以把一个测试套件整个添加到两一个测试套件中,就像小箩筐装进大箩筐变成一个筐一样。
套件机制用于将测试从逻辑上分组并将这些测试作为一个单元测试来运行。Junit4为了替代老版本的套件测试,套件被两个新注解替代:@RunWith、@SuteClasses。通过@RunWith指定一个特殊的运行器,即Suite.class套件运行器,并通过@SuiteClasses注解,将需要进行测试的类列表作为参数传入。
创建步骤说明:
* 创建一个空类作为测试套件的入口(这个空类必须使用public修饰符,而且存在无参构造函数);
* 将@RunWith、@SuiteClasses注释修饰这个空类;
* 把Suite.class作为参数传入@RunWith注释,以提示Junit将此类指定为运行器;
* 将需要测试的类组成数组作为@SuiteClasses的参数。
6)断言:Assertions
断言(assertions)是测试框架里面的若干个方法,用来判断某个语句的结果是否为真或判断是否与预期相符。