一、JUnit Test
(一)单元测试的概念
1 . 单元测试是针对最小的功能单元编写测试代码
2 . Java程序最小的功能单元是方法
3 . 单元测试就是针对单个Java方法的测试
(二)测试驱动开发
测试驱动开发TDD:Test-Driven Development
在TDD开发模式下,在编写一个接口之后,便可以立即编写一个测试,之后再编写实现类,之后运行测试。如果测试没有通过,就继续返回并修改代码,直至测试通过,完成整个测试任务。
(三)main方法测试的缺点
1 . 只能存在一个main方法,不能将测试代码分离
2 . 没有打印出测试结果和期望结果
所以就需要一种测试框架来帮助我们编写测试,这就产生了单元测试的概念
(四)单元测试的优点
1 . 可以确保单个方法运行正常
2 . 如果修改了方法代码,只需确保其对应的单元测试通过
3 . 测试代码本身就可以作为示例代码,用来演示如何调用该方法
4 . 可以自动化运行所有测试并获得报告(报告可以统计成功的测试与失败的测试,还可以统计代码覆盖率)
(五)JUnit单元测试框架
1 . JUnit的概念
JUnit是一个开源的Java语言单元测试框架:
- 专门针对Java语言设计,使用最为广泛
- JUnit是事实上的标准单元测试框架
2 . JUnit的特点
- 可以使用断言(Assertion)来测试期望结果
- 可以方便的组织和运行测试
- 可有方便的查看结果
- 常用IDE都集成了JUnit
- 可以方便的继承到Maven中
3 . JUnit的设计
- TestCase:一个TestCase代表一个测试
- TestSuite:一个TestSuite包含一组TestCase,代表一组测试
- TestFixture:一个TestFixture代表一个测试环境
- TestResult:用于收集测试结果
- TestRunner:用于运行测试
- TestListener:用于监听测试过程,收集测试数据
- Assert:用于断言测试结果是否正确
4 . 使用Assert断言
在使用Assert断言时,常使用import static org.junit.Assert.*;
- 断言相等:assertEquals(100, x)
- 断言数组内容相等:assertArrayEquals({1, 2, 3 }, x)
- 断言浮点数相等:assertEquals(3.1416, x, 0.0001)(必须设置误差值)
- 断言为null:assertNull(x)
- 断言真伪性:assertTrue(x > 0)/assertFalse(x < 0)
- 其他断言:assertNotEquals/assertNotNull等
(六)编写单元测试
- 一个TestSuite包含一组相关的测试方法
- 使用assert断言测试结果(注意浮点数assertEquals要指定delta)
- 每个测试方法必须完全独立
- 测试代码必须非常简单
- 不能为测试代码再编写测试
- 测试需要覆盖各种输入条件,特别是边界条件
二、使用JUnit
(一)使用Before与After
1 . FixTure
在JUnit中,同一个测试单元内的多个测试方法,在测试前都需要初始化某些对象,并且在测试后可能需要清理资源fileInputStream.close(),也就是Test FixTure(初始化测试资源)
在JUnit中
- 使用@Before方法初始化测试资源
- 使用@After方法清理测试资源
public class CalculatorTest {
Calculator calc;
@Before
//初始化测试资源
public void setUp() {
calc = new Calculator();
}
@After
//清理测试资源
public void tearDown() {
calc = null;
}
@Test
public void testCalcAdd2Numbers() {
assertEquals(3, calc.calculate("1+2"));
}
}
2 . 执行方法
JUnit对于每一个@Test方法:
- 首先实例化方法
- 执行@Before方法
- 执行@Test方法
- 执行@After方法
所以使用@Before和@After方法可以保证:
- 单个@Test方法在执行测试前都会创建新的Test实例
- 实例变量的状态不会传递给下一个@Test方法
- 单个@Test执行前后便会执行@Before和@After方法
在编写代码时,@Before方法初始化的对象存放在实例字段中,因为实例字段的状态不会影响到下一个@Test
3 . @BeforeClass和@AfterClass静态方法
- 在执行所有@Test之前,会执行@BeforeClass静态方法
- 之后进行所有测试
- 在执行所有@Test之后,执行@AfterClass静态方法
注意:
- @BeforeClass静态方法初始化的对象只能存放在静态字段中
- 静态字段的状态会影响到所有@Test
4 . JUnit Fixture
- @Before:初始化测试对象,例如:Input = new FileInputStream();
- @After:销毁@Before创建的测试对象,例如:input.close();
- @BeforeClass:初始化非常耗时的资源,例如:创建数据库
- @AfterClass:清理@BeforeClass创建的资源,例如:删除数据库
下面是一段测试代码:
import static org.junit.Assert.*;
import org.junit.Test;
public class CalculatorTest {
@Test
public void testCalcAdd2Numbers() {
Calculator calc = new Calculator();
int r = calc.calculate("1+2");
assertEquals(3, r);
}
@Test
public void testCalcAdd3Numbers() {
Calculator calc = new Calculator();
int r = calc.calculate("1+2+5");
assertEquals(8, r);
}
@Test
public void testCalcAddLargeNumbers() {
Calculator calc = new Calculator();
int r = calc.calculate("123+456");
assertEquals(579, r);
}
@Test
public void testCalcWithWhiteSpaces() {
Calculator calc = new Calculator();
int r = calc.calculate("1 + 5 + 10 ");
assertEquals(16, r);
}
}
我们可以将通用部分改写:
package com.feiyangedu.sample;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
public class CalculatorTest {
Calculator calc;
@Before
public void setUp() {
calc = new Calculator();
}
@Test
public void testCalcAdd2Numbers() {
int r = calc.calculate("1+2");
assertEquals(3, r);
}
@Test
public void testCalcAdd3Numbers() {
int r = calc.calculate("1+2+5");
assertEquals(8, r);
}
@Test
public void testCalcAddLargeNumbers() {
int r = calc.calculate("123+456");
assertEquals(579, r);
}
@Test
public void testCalcWithWhiteSpaces() {
int r = calc.calculate("1 + 5 + 10 ");
assertEquals(16, r);
}
}
可以看到在上面改写之后的代码中,可以让每一个@Test共享@Before的资源,然后便可以删除每一个@Test中需要初始化的资源,方便进行单元测试和修改代码。
(二)异常测试
在Java中,我们需要对可能抛出的异常进行测试,由于异常本身是方法签名的一部分,所以需要我们测试错误的输入是否会导致特定的异常。
1 . 如何测试异常
在Java中可以使用try…catch语句来捕获异常,可是如果需要捕获的异常较多,就需要我们编写大量的try…catch语句代码。
所以我们测试异常还可以使用@Test(expected=Exception.class)
@Test(expected = NumberFormatException.class)
public void testNumberFormatException() {
Integer.parseInt(null);
}
在上面这段代码中,我们使用expected来指定异常的class。
- 在测试代码中,如果抛出了指定类型的异常,则测试通过
- 如果没有抛出指定类型的异常,或者抛出的异常类型错误,则测试失败
- 对可能发生的每种类型的异常进行测试
(三)参数化测试
如果待测试的输入和输出是一组数据,可以把测试数据组织起来,用不同的测试数据调用相同的测试方法,这种方法称为参数化测试。
在参数化测试过程中:
- 参数必须由静态方法date()返回
- 返回类型必须为Collection<Object[]>
- 静态方法必须标记为@Pararmeters
- 测试类必须标记为@RunWith(Parameterized.class)
- 构造方法参数必须与测试参数相对应
可以通过@Parameter(index)标记public字段,这样就不必去编写构造方法。JUnit会自动创建@Test实例,然后将参数注入到public字段中。
package com.feiyangedu.sample;
import static org.junit.Assert.*;
import java.util.Arrays;
import java.util.Collection;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
//标记@RunWith
public class CalculatorTest {
@Parameters
//标记@Patameters注解
//定义静态方法data(),返回类型是Collection,返回List类型的数据
public static Collection<?> data() {
return Arrays
.asList(new Object[][] { { "1+2", 3 }, { "1+2+5", 8 }, { "123+456", 579 }, { " 1 + 5 + 10 ", 16 } });
}
Calculator calc;
@Parameter(0)
public String input;
@Parameter(1)
public int expected;
@Before
//初始化测试资源
public void setUp() {
calc = new Calculator();
}
@Test
//编写测试方法
public void testCalculate() {
int r = calc.calculate(this.input);
assertEquals(this.expected, r);
}
}
(四)超时测试
在JUnit测试中,可以为单个测试设置超时:
例如:超时设置为1秒:@Test(timeout=1000)(这里的计量单位是毫秒)
注意:超时测试不能取代性能测试和压力测试