测试的粒度
单元测试:测试每一个单独的模块。
集成测试:测试模块之间的交互。
系统测试:由开发人员对整个系统进行测试。
验收测试:由客户根据用户需求对系统进行验证,通常没有正式的测试用例。
单元测试
对软件中的基本模块进行测试。
例如:
- 一个函数
- 一个类
- 一个组件
单元测试通常能发现的问题:
- 局部数据结构问题
- 算法问题
- 边界条件问题
- 错误处理问题
为什么要进行单元测试?
采用 分而治之 的方法:
- 将系统拆分为多个单元。
- 分别调试每个单元。
- 缩小 bug 可能存在的范围。
- 不希望在其他单元中到处追踪 bug。
也就是说,单元测试的核心思想是:先保证小模块正确,再逐步构建更大的系统。
如何进行单元测试?
按层次构建系统:
- 从不依赖其他类的类开始测试。
- 然后在已经测试过的类的基础上,继续测试更高层的类。
好处:
- 避免编写过多的 mock 类。
- 当测试某个模块时,它依赖的模块已经比较可靠。
单元测试框架
- xUnit
- JUnit
测试程序
public class IMath {
/**
* 返回 x 的平方根的整数部分
* 也就是舍弃小数部分
*/
public int isqrt(int x) {
int guess = 1;
while (guess * guess < x) {
guess++;
}
return guess;
}
}第一种测试写法
import org.junit.Test;
import static org.junit.Assert.*;
/** A JUnit test class to test the class IMath. */
public class IMathTestJUnit1 {
/** A JUnit test method to test isqrt. */
@Test
public void testIsqrt() {
IMath tester = new IMath();
assertTrue(0 == tester.isqrt(0));
assertTrue(1 == tester.isqrt(1));
assertTrue(1 == tester.isqrt(2));
assertTrue(1 == tester.isqrt(3));
assertTrue(10 == tester.isqrt(100));
}
/** Other JUnit test methods */
}这种写法还不够好,因为assertTrue(0 == tester.isqrt(0));只告诉JUnit一个布尔值,如果测试失败,JUnit很难给出详细
信息。
第二种写法
import org.junit.Test;
import static org.junit.Assert.*;
/** A JUnit test class to test the class IMath. */
public class IMathTestJUnit2 {
/** A JUnit test method to test isqrt. */
@Test
public void testIsqrt() {
IMath tester = new IMath();
assertEquals(0, tester.isqrt(0));
assertEquals(1, tester.isqrt(1));
assertEquals(1, tester.isqrt(2));
assertEquals(1, tester.isqrt(3));
assertEquals(10, tester.isqrt(100));
}
/** Other JUnit test methods */
}assertEquals(expected, actual)
还可以更好!
第三种写法
import org.junit.Test;
import static org.junit.Assert.*;
/** A JUnit test class to test the class IMath. */
public class IMathTestJUnit3 {
/** A JUnit test method to test isqrt. */
@Test
public void testIsqrt() {
IMath tester = new IMath();
assertEquals("square root for 0 ", 0, tester.isqrt(0));
assertEquals("square root for 1 ", 1, tester.isqrt(1));
assertEquals("square root for 2 ", 1, tester.isqrt(2));
assertEquals("square root for 3 ", 1, tester.isqrt(3));
assertEquals("square root for 100 ", 10, tester.isqrt(100));
}
/** Other JUnit test methods */
}assertEquals("提示信息", 期望值, 实际值);这样就更容易知道是哪一个输入出了问题。
然而还是有问题,如果有一个测试失败,后面的测试都不会进行
多个测试输入放在同一个测试方法中,失败信息不够完整,只能看到第一个失败点。
第四种写法
@Test
public void testIsqrt1() {
assertEquals("square root for 0 ", 0, tester.isqrt(0));
}
@Test
public void testIsqrt2() {
assertEquals("square root for 1 ", 1, tester.isqrt(1));
}
@Test
public void testIsqrt3() {
assertEquals("square root for 2 ", 1, tester.isqrt(2));
}可以看出,第四种写法还是有很多问题,我们写了很多相似的结构。
参数化测试
参数化测试的做法是:
只写一个测试逻辑 test m1(),然后把不同输入作为参数传进去。
同一个测试方法 + 多组测试数据。
@RunWith(Parameterized.class)
public class IMathTestJUnitParameterized {
private IMath tester;
private int input;
private int expectedOutput;
/** 构造方法:接收每一组输入-输出对 */
public IMathTestJUnitParameterized(int input, int expectedOutput) {
this.input = input;
this.expectedOutput = expectedOutput;
}
@Before
/** 创建测试夹具的初始化方法 */
public void initialize() {
tester = new IMath();
}
@Parameterized.Parameters
/** 存储输入-输出对,也就是测试数据 */
public static Collection<Object[]> valuePairs() {
return Arrays.asList(new Object[][] {
{ 0, 0 },
{ 1, 1 },
{ 2, 1 },
{ 3, 1 },
{ 100, 10 }
});
}
@Test
/** 参数化的 JUnit 测试方法 */
public void testIsqrt() {
assertEquals(
"square root for " + input + " ",
expectedOutput,
tester.isqrt(input)
);
}
}但是,并不是所有测试都可以抽象成参数化测试。
参数化测试适用于相同测试逻辑 + 不同测试数据
但如果不同测试用例内部调用的方法顺序不同,或者测试流程本身不同,就不适合简单抽象成参数化测试。
例如:
public class ArrayList {
...
/** 返回当前 list 的大小 */
public int size() {
...
}
/** 向 list 中添加一个元素 */
public void add(Object o) {
...
}
/** 从 list 中移除一个元素 */
public void remove(int i) {
...
}
}JUnit 测试套件
一个测试套件是一组测试,或者是一组其他测试套件。
- 将多个测试组织成一个更大的测试集合。
- 帮助实现自动化测试。
断言
| 断言 | 说明 |
|---|---|
fail([msg]) | 使测试方法失败,msg 是可选提示信息 |
assertTrue([msg], bool) | 检查布尔条件是否为真 |
assertFalse([msg], bool) | 检查布尔条件是否为假 |
assertEquals([msg], expected, actual) | 检查期望值和实际值是否相等 |
assertNull([msg], obj) | 检查对象是否为 null |
assertNotNull([msg], obj) | 检查对象是否不为 null |
assertSame([msg], expected, actual) | 检查两个变量是否引用同一个对象 |
assertNotSame([msg], expected, actual) | 检查两个变量是否引用不同对象 |
