MIT_6.031之测试

测试的基本概念与方法

1. 测试的重要性

  • 测试的目标是尽早发现程序中的错误。通过频繁和及时的测试,能够在开发的早期阶段识别和修复问题,避免后期的成本增加。
  • 测试的频率:开发时就要进行测试,保持测试驱动开发(TDD)的思路。

2. 单个功能开发的步骤

  • 编写功能说明。
  • 编写符合规范的测试用例
  • 编写可以通过测试的代码。

3. 分区选择测试用例

  • 原则:测试用例应该足够小以快速运行,同时足够全面验证程序的行为。
  • 输入空间分区:将输入空间划分为不同的子域,每个子域代表一组输入。在每个子域中选择一个测试用例。
  • 子域的划分目标是让程序在子域中产生相似的行为。
例子:测试 BigInteger.multiply()
1
public BigInteger multiply(BigInteger val);

用法

1
2
3
BigInteger a = ...;
BigInteger b = ...;
BigInteger ab = a.multiply(b);

multiply 是两个参数的函数: $ : $

  • 分区设计
    1. \(a\)\(b\) 均为正
    2. \(a\)\(b\) 均为负
    3. \(a\) 为正,\(b\) 为负
    4. \(a\) 为负,\(b\) 为正
    5. 特殊情况:0、1、-1
    6. 使用大于 long 的巨大整数

常见测试输入组合

  • \(0, 1, -1, \text{小正整数}, \text{小负整数}, \text{巨大的正整数}, \text{巨大的负整数}\)

通过分区,共有 49 种组合。

4. 考虑边界情况

  • 边界值测试:错误通常发生在不同子域之间的边界处,所以边界值是测试的关键。
  • 常见边界:
    • 0 是正数和负数之间的边界
    • 数据类型的最大值和最小值,例如 Integer.MAX_VALUEInteger.MIN_VALUE
    • 集合类型的空状态(如空字符串、空列表、空数组)
    • 集合的第一个和最后一个元素
例子:测试 public static int max(int a, int b)
  • 测试用例:
    1. (1, 2):测试 \(a < b\),且 \(a > 0\)\(b > 0\)
    2. (-1, -3):测试 \(a > b\),且 \(a < 0\)\(b < 0\)
    3. (0, 0):测试 \(a = b\),且 \(a = 0\)\(b = 0\)
    4. (Integer.MIN_VALUE, Integer.MAX_VALUE):测试 \(a < b\),且 \(a = \text{minint}\)\(b = \text{maxint}\)
    5. (Integer.MAX_VALUE, Integer.MIN_VALUE):测试 \(a > b\),且 \(a = \text{maxint}\)\(b = \text{minint}\)

5. 单元测试

  • 使用 JUnit 进行单元测试:
    • 通过 assertEquals(expect, actual) 验证测试结果是否符合预期。
    • 每个测试方法的上方应包含注释,说明测试用例的选择依据。
例子:空字符串测试
1
2
3
4
// covers test.length() = 0, start = 0 = text.length(), text.length()-start = 0
@Test public void testEmpty() {
assertEquals("", reverseEnd("", 0));
}

6. 黑盒与白盒测试

  • 黑盒测试:根据功能规范编写测试用例,不考虑代码实现细节。
  • 白盒测试:基于代码实现的测试,关注程序内部的逻辑结构。
白盒测试实例
1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* Sort a list of integers in nondecreasing order.
* Modifies the list so that values.get(i) <= values.get(i+1) for all 0<=i<values.length()-1
*/
public static void sort(List<Integer> values) {
if (values.size() < 10) {
radixSort(values);
} else if (values.size() < 1000000000) {
quickSort(values);
} else {
mergeSort(values);
}
}

测试当数组长度为 10 的情况(这种情况只有通过代码分析才能得知):

1
values = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0];

7. 覆盖范围

  • 语句覆盖:每个语句是否都被测试运行过。
  • 分支覆盖:每个 ifwhile 语句的条件是否都被测试过。
  • 路径覆盖:是否测试了所有可能的执行路径。
工具推荐
  • EclEmma 是一个很好的 Eclipse 代码覆盖工具。

8. 集成测试

  • 集成测试用于验证模块之间的连接是否正常工作。即使每个模块单独工作正常,模块组合在一起时也可能会出现错误。

9. 自动化测试

  • 自动化测试可以频繁运行测试并自动检查其结果,防止程序在修复错误或添加功能时出现新的问题(即程序的退化)。
  • 回归测试:修复错误后,将错误样例加入自动化测试中,以防止同类错误再次出现。

10. 总结

  • 测试驱动开发:先编写测试,再编写功能代码。
  • 系统选择测试用例:通过分区和边界进行选择。
  • 白盒测试:用以填补测试套件中的缺口,确保代码的全面覆盖。
  • 自动化回归测试:确保错误不会复发。

优质程序的三个属性

  1. 避免错误:通过测试尽早发现问题。
  2. 容易理解:代码清晰且具备可读性,便于维护。
  3. 准备好进行更改:代码设计良好,能够在需求变化时快速修改。