软件测试中什么是可追溯性矩阵?没有它,你的测试就是瞎子
你花了2个月写测试用例,上线第一天就崩了。原因:开发改了一个需求,测试完全不知道。这不是测试不努力,而是没有可追溯性矩阵(RTM)。
可追溯性矩阵,是软件测试中连接需求、用例和缺陷的“活地图”。它让每一个需求都有对应的测试,每一个测试都有明确的目标。
没有可追溯性,测试就是盲人摸象。
---
01 可追溯性矩阵是什么?一张表格干掉80%的测试盲区
可追溯性矩阵(Requirements Traceability Matrix,简称RTM)本质上是一张表格。这张表格记录了三件事:需求是什么、谁测试了它、测试结果如何。
你以为测试就是写用例、执行、报bug?错了。真正的测试需要回答:这个需求被测试覆盖了吗?覆盖率是多少?哪个需求出了问题?
RTM就是用来回答这些问题的。
一个真实的RTM长什么样?
| 需求ID | 需求描述 | 用例ID | 用例标题 | 执行结果 | 缺陷ID | 状态 |
|---|---|---|---|---|---|---|
| REQ-001 | 用户登录 | TC-001 | 验证正确账号密码登录 | 通过 | 无 | 已完成 |
| REQ-001 | 用户登录 | TC-002 | 验证错误密码提示 | 失败 | BUG-001 | 待修复 |
| REQ-002 | 商品搜索 | TC-003 | 验证关键词搜索 | 通过 | 无 | 已完成 |
这张表,就是RTM的核心骨架。
为什么RTM是测试的“命根子”?
- ►需求覆盖: 一个需求对应多个测试用例,覆盖率达到100%才算合格
- ►缺陷溯源: 每个bug都能追溯到哪个需求、哪个用例
- ►变更影响: 需求改了,立刻知道哪些用例需要重测
测试不做RTM,等于打战没有地图。
---
02 没有RTM的测试团队,3个致命问题
我见过太多团队,测试用例写了上千条,需求变更时直接懵了。没有RTM,测试就是“蒙眼狂奔”。
问题1:需求变更,测试不知情
开发说:“这个需求改了一点。”测试问:“改了哪里?”开发说:“就改了一个字段。”结果测试上线前才发现,这个“一点”影响了5个核心功能。
数据: 某项目上线后出现10个严重bug,其中8个是因为需求变更未同步到测试。
问题2:测试覆盖率是假的
你问测试经理:“覆盖率多少?”他答:“95%。”你再问:“哪些需求没测?”他答:“不知道。”
没有RTM,覆盖率就是个数字游戏。你测了100个用例,但核心需求可能一个没测到。
问题3:缺陷无法定位
上线后用户反馈:“这个页面打不开。”测试查了半天,找不到对应需求。因为bug报告里没有需求ID,没有用例ID,只有一句“页面报错”。
结论: 没有RTM的测试,就是“黑箱测试”——你不知道测了什么,也不知道漏了什么。
覆盖率不是数字,是可追溯性。
---
03 手把手教你构建RTM:从需求到缺陷全链路追踪
构建RTM不是写一个Excel,而是建立一套追踪体系。分为3步:
假设需求列表
requirements = [
{"req_id": "REQ-001", "description": "用户登录"},
{"req_id": "REQ-002", "description": "商品搜索"},
{"req_id": "REQ-003", "description": "购物车功能"}
]
假设用例列表
test_cases = [
{"tc_id": "TC-001", "req_id": "REQ-001", "title": "验证正确账号密码登录"},
{"tc_id": "TC-002", "req_id": "REQ-001", "title": "验证错误密码提示"},
{"tc_id": "TC-003", "req_id": "REQ-002", "title": "验证关键词搜索"},
{"tc_id": "TC-004", "req_id": "REQ-003", "title": "验证添加商品到购物车"}
]
生成RTM
rtm_data = []
for tc in test_cases:
req = next(r for r in requirements if r["req_id"] == tc["req_id"])
rtm_data.append({
"需求ID": tc["req_id"],
"需求描述": req["description"],
"用例ID": tc["tc_id"],
"用例标题": tc["title"]
})
rtm_df = pd.DataFrame(rtm_data)
print(rtm_df)
需求ID 需求描述 用例ID 用例标题
0 REQ-001 用户登录 TC-001 验证正确账号密码登录
1 REQ-001 用户登录 TC-002 验证错误密码提示
2 REQ-002 商品搜索 TC-003 验证关键词搜索
3 REQ-003 购物车功能 TC-004 验证添加商品到购物车
假设缺陷列表
bugs = [
{"bug_id": "BUG-001", "tc_id": "TC-002", "description": "错误密码提示不正确"}
]
def get_bug_id(tc_id):
for bug in bugs:
if bug["tc_id"] == tc_id:
return bug["bug_id"]
return "无"
rtm_df["缺陷ID"] = rtm_df["用例ID"].apply(get_bug_id)
print(rtm_df)
需求ID 需求描述 用例ID 用例标题 执行结果 缺陷ID
0 REQ-001 用户登录 TC-001 验证正确账号密码登录 通过 无
1 REQ-001 用户登录 TC-002 验证错误密码提示 失败 BUG-001
2 REQ-002 商品搜索 TC-003 验证关键词搜索 通过 无
3 REQ-003 购物车功能 TC-004 验证添加商品到购物车 未执行 无
import pandas as pd
读取需求
req_df = pd.read_excel("requirements.xlsx")
读取用例
tc_df = pd.read_excel("test_cases.xlsx")
读取执行结果
exec_df = pd.read_excel("execution_results.xlsx")
合并生成RTM
rtm = pd.merge(tc_df, req_df, on="需求ID", how="left")
rtm = pd.merge(rtm, exec_df, on="用例ID", how="left")
输出覆盖率报告
coverage = rtm.groupby("需求ID").agg({
"用例ID": "count",
"执行结果": lambda x: (x == "通过").sum()
})
coverage["覆盖率"] = coverage["执行结果"] / coverage["用例ID"] * 100
print(coverage)
用例ID 执行结果 覆盖率
需求ID
REQ-001 2 1 50.000000
REQ-002 1 1 100.000000
REQ-003 1 0 0.000000