1 . 单元测试是针对最小的功能单元编写测试代码
2 . Java程序最小的功能单元是方法
3 . 单元测试就是针对单个Java方法的测试
测试驱动开发TDD:Test-Driven Development
在TDD开发模式下,在编写一个接口之后,便可以立即编写一个测试,之后再编写实现类,之后运行测试。如果测试没有通过,就继续返回并修改代码,直至测试通过,完成整个测试任务。
1 . 只能存在一个main方法,不能将测试代码分离
2 . 没有打印出测试结果和期望结果
所以就需要一种测试框架来帮助我们编写测试,这就产生了单元测试的概念
1 . 可以确保单个方法运行正常
2 . 如果修改了方法代码,只需确保其对应的单元测试通过
3 . 测试代码本身就可以作为示例代码,用来演示如何调用该方法
4 . 可以自动化运行所有测试并获得报告(报告可以统计成功的测试与失败的测试,还可以统计代码覆盖率)
JUnit是一个开源的Java语言单元测试框架:
在使用Assert断言时,常使用import static org.junit.Assert.*;
在JUnit中,同一个测试单元内的多个测试方法,在测试前都需要初始化某些对象,并且在测试后可能需要清理资源fileInputStream.close(),也就是Test FixTure(初始化测试资源)
在JUnit中
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"));
}
}
JUnit对于每一个@Test方法:
所以使用@Before和@After方法可以保证:
在编写代码时,@Before方法初始化的对象存放在实例字段中,因为实例字段的状态不会影响到下一个@Test
下面是一段测试代码:
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中,我们需要对可能抛出的异常进行测试,由于异常本身是方法签名的一部分,所以需要我们测试错误的输入是否会导致特定的异常。
在Java中可以使用try…catch语句来捕获异常,可是如果需要捕获的异常较多,就需要我们编写大量的try…catch语句代码。
所以我们测试异常还可以使用@Test(expected=Exception.class)
@Test(expected = NumberFormatException.class)
public void testNumberFormatException() {
Integer.parseInt(null);
}
在上面这段代码中,我们使用expected来指定异常的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)(这里的计量单位是毫秒)
注意:超时测试不能取代性能测试和压力测试