主要内容:
● 概述单元测试相关概念及意义
● TestNG、Mockito、Unitils测试框架简介
● 使用TestNG、Mockito、Unitils及Spring进行单元测试
● 面向数据库应用的测试
1.概述单元测试
按照软件工程思想,软件测试可以分为单元测试、集成测试、功能测试、系统测试。功能测试和系统测试一般来说是测试人员的职责,但单元测试和集成测试则必须由开发人员保证。
1.1为什么需要单元测试?
软件测试阶段通过人工或自动手动来运行或测试某个系统的过程,目的在于检验它是否满足规定的需求或弄清楚预期结果与实际结果的差别。
单元测试
单元测试是开发者编写的一小段代码,用于检验目标代码的一个很小的、很明确的功能是否正确。一个单元测试通常用于判断某个特定条件或特定场景下某个特定函数的行为。例如:用户可能把一个很大的值放入一个有序的List中,然后确认该值出现在List的尾部。
程序员有责任编写功能代码,同时也有责任为自己的代码编写单元测试。执行单元测试,就是为了证明这段代码的行为和我们期望的一致。
一般情况下,一个功能模块往往会调用其他功能模块来完成某项功能,如业务层的业务类可能会调用多个DAO来完成某项业务。对某个功能模块进行单元测试时,我们希望拼比对外在功能模块的依赖,以便将焦点放在目标功能模块的测试上。这时,模拟对象是最有力的工具,它根据外在模块的接口模拟特定的操作行为,这样单元测试就可以在假设关联模块正确工作的情况下验证本模块的逻辑正确性。
集成测试
集成测试在功能模块开发完成后,为验证功能模块之间匹配调用的正确性而进行的测试。如:当对UserService业务层的类进行单元测试时,可以通过创建UserDao和LoginLogDao模拟对象,在假设DAO类正确工作的情况下对UserService进行测试。而对UserService进行集成测试时,应注入真实的UserDao和LoginLogDao进行测试。
1.2.单元测试的好处
- 软件质量最简单、最有效的保证。
- 是目标代码最清晰、最有效的文档。
- 可以优化目标代码的设计。
- 是代码重构的保障。
1.3 单元测试的误解
- 影响开发进度
- 增加开发成本
- 我是个编程高手,无需进行单元测试
- 测试人员会测出所有的bug
2.TestNG快速进阶
编写一个测试的过程由3个典型步骤。
1.编写测试的业务逻辑并在代码中插入TestNG注解
2.将测试信息添加到testng.xml或者build.xml文件中
3.运行TestNG
2.1TestNG生命周期
完整生命周期:类级初始化资源处理、方法级初始化资源处理、执行测试用例中的方法、方法级销毁资源处理、类级销毁资源处理。
类级初始化、销毁资源处理方法在一个测试用例类中只能运行一次;
方法级初始化、销毁资源处理方法在执行测试用例的每个测试方法中都会运行一次,以防止测试方法相互之间的影响。
如果在一个测试用例中编写了多个初始化处理方法,则运行时先执行位于最后面的初始化方法,然后往前一个个执行初始化方法。对于多个销毁资源处理方法,则按照方法的顺序一个个往后执行。
2.2使用TestNG
1.测试方法
使用@Test注解来标注一个测试方法。此外可以引用Java5.0的静态导入功能导入断言Assert类,方便地在测试方法中使用断言方法。
public class TestNGTest {
private Car audi;
private Car benz;
private Car bmw;
@BeforeMethod
protected void setUp() {
audi = new Car("yellow","audi",180);
benz = new Car("white","benz",220);
bmw = new Car("black","bmw",190);
}
@Test
private void getBean() {
Assert.assertEquals("audi", audi.getBrand());
}
}
在TestNG中,在测试方法添加@Test注解即可。
@BeforeClass @AfterClass 在一个Test类的所有测试方法执行前后各执行一次
@BeforeClass/@AfterClass | @BeforeMethod/@AfterMethod |
---|---|
在一个类中只能出现一次 | 在一个类可出现多次,执行顺序不确定 |
方法名不限制 | 方法名不限制 |
类中只执行一次 | 在每个测试方法之前或者之后都会执行一次 |
@BeforeClass父类中标识了该注解的方法将会先于当前类中标识了该注解的方法执行。 @AfterClass父类中标识了该注解的方法将会在当前类中标识了该注解的方法之后执行。 |
@BeforeMethod父类中标识了该注解的方法将会先于当前类中标识了该注解的方法执行。 @AfterMethod父类中标识了该注解的方法将会在当前类中标识了该注解的方法之后执行。 |
必须声明为public static | 必须声明为public,并且非static |
所有标识为@AfterClass的方法一定会被执行,即使标识为@BeforeClass的方法抛出异常的情况下也会执行 | 所有标识为@AfterMethod的方法一定会被执行,即使标识为@BeforeMethod或@Test的方法抛出异常的情况下也会执行 |
2.异常测试
通过对@Test传入expected参数值,即可测试异常。3.超时测试
通过对@Test注解中为timeOut参数指定时间值,即可进行超时测试。
如果测试运行时间超过指定的毫秒数,即测试失败。超时测试对网路链接类非常重要。
4.参数化测试
为了测试程序的健壮性,可能需要模拟不同的参数对方法进行测试。如果每个类型的参数创建一个测试方法,很难接受。TestNG提供参数化测试。
package sample.testng;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import static org.testng.Assert.*;
import org.testng.annotations.*;
public class TestNGParameterTest {
private SimpleDateFormat simpleDateFormat;
@DataProvider(name = "testParam")
public static Object[][] getParamters() {
String[][] params = {
{ "2016-02-01 00:30:59", "yyyyMMdd", "20160201" },
{ "2016-02-01 00:30:59", "yyyy年MM月dd日", "2016年02月01日" },
{ "2016-02-01 00:30:59", "HH时mm分ss秒", "00时30分59秒" } };
return params;
}
@Test(dataProvider = "testParam")
public void testSimpleDateFormat(String date,String dateformat,String expectedDate) throws ParseException{
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d = df.parse(date);
simpleDateFormat = new SimpleDateFormat(dateformat);
String result = simpleDateFormat.format(d);
assertEquals(result,expectedDate);
}
}
首先编写测试类的参数数据提供者方法,然后用此方法进行参数初始化。该方法返回一个Object[][]类型。用@DataProvider注解标识该方法并设置name。之后在需要测试的方法中设置@Test(dataProvider="")属性。
5.分组测试
TestNG支持执行复杂的分组测试。
@Test(groups = {"class-group"})
public class TestNGGroupsTest {
@Test(groups = {"group1", "group2"})
public void testMethod1() {
}
@Test(groups = {"group1", "group2"})
public void testMethod2() {
}
@Test(groups = {"group1"})
public void testMethod3() {
}
}
6.依赖测试
如果测试方法需要按照一个特定的顺序被调用。TestNG为@Test注解提供了dependsOnMethods或dependsOnGroups属性来实现测试方法间的依赖关系。
public class TestNGDependsTest {
@Test
public void testMethod1() {
}
@Test
public void testMethod2() {
assertNotNull(new User());
}
@Test(dependsOnMethods = {"testMethod1","testMethod2"},alwaysRun=true)
public void testMethod3() {
}
}
当执行testMethod3()测试方法时,会先调用两个依赖的测试方法。如果testMethod1()或testMethod2()任意一个失败都不会执行testMethod3()。这种依赖为强依赖,默认。可以通过@Test的alwaysRun=true改变,则testMethod3()总会执行。