J04 测试

验证与确认 (Vérification et Validation)


  • 验证 (Vérification)
    • 确认我们是否正确地制作了产品。
    • 软件是否符合其规格说明?
  • 确认 (Validation)
    • 确认我们是否制作了正确的产品。
    • 软件是否满足用户需求?

验证方法 (Méthodes de vérification)


  • 检查 (Inspection)
    • 静态测试,包括规格说明和代码的审查。
  • 测试 (Test)
    • 动态测试,通过输入数据运行代码。

符号验证 (Vérification symbolique)

  • 抽象执行,生成输入数据或验证特定点(如代码覆盖、计算精度等)。

形式验证 (Vérification formelle)

  • 基于证明或模型检测(Model Checking)。

为什么要测试?(Pourquoi tester ?)


  • 研究表明,设计阶段修复错误的成本与生产阶段相比,成本比为 1:100。
  • 在开发 NASA 卫星嵌入式软件时,测试占开发预算的 80%(管理信息系统的标准是 35%)。

测试的难点 (Difficultés des tests)


  • 测试是一种部分验证的方法。
  • 有时被认为是负面方法:旨在检测错误,而非构建性开发活动的一部分。

术语 (Terminologie)


  • 异常 (Anomalie)
    • 系统行为不符合规格说明。
  • 缺陷 (Défaut)
    • 代码片段在运行时可能导致异常。
  • 错误 (Erreur)
    • 导致缺陷的人为行为。

不同类型的测试 (Les différents tests)


  • 单元测试 (Tests unitaires)
    • 测试单个组件的独立行为。
    • 组件可以是一个函数或方法。
    • 隔离测试:组件需要在完全隔离的环境下测试(不访问文件、数据库等)。
      • 如果组件依赖于其他服务,使用桩 (stubs) 或模拟对象 (mock objects) 来代替。
  • 集成测试 (Tests d'intégration)
  • 非回归测试 (Tests de non régression)
  • 名义测试 (Tests nominaux)
  • 鲁棒性测试 (Tests de robustesse)
  • 性能测试 (Tests de performance)

输入数据的选择 (Choix des données d'entrée)


  • 基于规格说明
    • 功能性测试:黑盒测试方法。
    • 数据选择基于被测模块的描述。
    • 难点:数据集合通常是无限的。
  • 基于代码
    • 结构性测试:白盒测试方法。
    • 数据选择需要覆盖模块中的所有代码路径至少一次。
    • 难点:组合爆炸问题(combinatorial explosion)。

黑盒测试 (Test boîte noire)

  • 利用规格说明,根据输入值预测输出结果。
  • 极限值测试(Valeurs limites)。
    • 空数组。
    • 仅有一个元素的数组(目标元素存在/不存在)。
    • 第一个或最后一个元素为目标。
  • 将输入划分为等价类,每类至少选择一个代表值。
  • 分类测试
    • 目标元素存在。
    • 目标元素不存在。
  • 不同大小的数组

白盒测试 (Test boîte blanche)

  • 基于代码知识的测试。
  • 执行每条指令和每个路径至少一次。
  • 构建控制流图,包括:
    • 节点:表示指令。
    • 边:表示分支。

圈复杂度 (Complexité cyclomatique)


  • 独立路径的数量,计算方式如下:
    • 边数 - 节点数 + 2。
    • 图的区域数。
    • 决策节点数 + 1。
  • 下界:执行所有独立路径所需的最少测试次数。
  • 上界:执行所有指令所需的最大测试次数。

JUnit 单元测试 (Tests unitaires en Java)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class TestBoiteNoire {
@Test
public void testTableauVide() {
int[] t = new int[0];
assertEquals(-1, RechercheDichotomique.recherche(123, t));
}

@Test
public void testTableauUnElementPresent() {
int[] t = new int[]{17};
assertEquals(0, RechercheDichotomique.recherche(17, t));
}
}

JUnit 断言 (Assertions)

  • assertEquals():验证两个对象相等。
  • assertSame():验证两个引用是否指向同一对象。
  • assertNull():验证对象是否为 null。
  • assertTrue():验证表达式是否为真。
  • assertArrayEquals():验证两个数组是否相等。
  • assertThrows():验证是否抛出异常。
  • assertTimeout():验证执行是否在规定时间内完成。

组件隔离 (Isoler un composant)


  • 单元测试的组件不应依赖其他组件。
  • 使用模拟对象(Mocks)代替实际依赖。
  • 示例框架:Mockito。

验证不变量、前置条件或后置条件

  • 使用 assert 语句。
1
2
3
assert tableau != null;
assert estTrie(tableau) : "表格必须是排序的";

启用断言

  • 通过 java -ea 命令。

测试驱动开发 (Développement piloté par les tests, TDD)


  • 一种先编写单元测试再编写代码的方法。

TDD 的步骤

  1. 建模测试对象(了解其公开方法)。
  2. 编写测试对象的框架代码(方法声明)。
  3. 循环迭代:
    • 编写测试。
    • 执行测试(应失败)。
    • 编写足够的业务代码以通过测试。

红绿重构循环 (Red, Green, Refactor)

  • 红色:测试失败。
  • 绿色:测试通过。
  • 重构:优化代码。