第1章 识别重复夹具:从模式到候选标记
在UI自动化测试中,夹具(Fixture)重复是测试代码膨胀的首要根源。同一份前置条件代码在多个测试类中反复出现,不仅增加维护成本,还导致环境配置不一致、用例执行结果不稳定。本章从重复模式分类、自动化扫描定位到候选标记,建立完整的识别体系。
1.1 重复夹具的典型模式
通过对不同规模测试代码库(10万~100万行)的实证分析,我们归纳出四种高频重复模式。检测难度从低到高排列:
| 模式类型 | 描述 | 代码特征 | 检测难度 |
|---|---|---|---|
| 完全复制 | 相同的前置条件代码块在多处原样出现,仅缩进或注释略有差异 | 逐行匹配率 > 95% | 低 |
| 结构重复 | AST 结构一致,仅变量名、字符串参数或等待时间不同 | 语法树结构相似度 > 85% | 中 |
| 组合重复 | 相同的夹具调用序列反复出现,如 login → navigate → 数据准备 | 方法调用序列完全一致 | 中 |
| 继承冗余 | 基类定义公共夹具,子类覆写时引入相同逻辑或重复调用 | 子类与基类存在语义重复的 setup/teardown | 高 |
实践建议:从“完全复制”模式入手建立识别基线,该模式占重复夹具总量的 40%~55%,修复性价比最高。
1.2 使用代码扫描工具定位重复
手工审查难以覆盖大型代码库,推荐采用两阶段扫描策略:先用通用重复代码检测工具做全量快扫,再用基于 AST 的自定义脚本做精确过滤。
阶段一:jscpd 全量扫描
# 全局安装 jscpd
npm install -g jscpd
# 对 tests 目录执行重复分析
# --min-lines: 最少重复行数,过滤偶然相同
# --min-tokens: 最少重复 数,控制灵敏度
# --format: 指定语言,提高 AST 精确度
jscpd --patterns "**/tests/**/*.py" \
--min-lines 5 \
--min-tokens 50 \
--format "python" \
--output "jscpd-report.json"
阶段二:AST 精确过滤
针对 jscpd 输出的候选结果,使用 Python AST 工具提取夹具函数体签名,剔除变量名差异导致的误报:
# fixture_duplicates.py
# 基于 AST 的夹具重复模式精检测
import ast
import glob
import json
from collections import defaultdict
def extract_fixture_sig(node):
"""提取夹具函数的方法体 AST 签名"""
if isinstance(node, ast.FunctionDef) and \
node.name.lower() in ('setup', 'teardown', 'set_up', 'tear_down',
'beforeeach', 'aftereach'):
# 忽略变量名,只保留结构
return ast.dump(node.body)
return None
def scan_duplicates(pattern="tests/**/*.py"):
files = glob.glob(pattern, recursive=True)
sig_map = defaultdict(list)
for fp in files:
with open(fp, 'r', encoding='utf-8') as f:
try:
tree = ast.parse(f.read())
except SyntaxError:
continue
for node in ast.walk(tree):
sig = extract_fixture_sig(node)
if sig:
sig_map[sig].append({
"file": fp,
"line": node.lineno,
"name": node.name
})
# 返回值:签名 → 位置列表(仅保留 >=2 处的重复组)
result = []
for sig, locs in sig_map.items():
if len(locs) >= 2:
result.append({
"signature_hash": hash(sig),
"occurrences": len(locs),
"locations": locs
})
return result
if __name__ == "__main__":
dups = scan_duplicates()
# 输出 JSON 供后续标记脚本使用
with open("fixture-candidates.json", "w") as out:
json.dump(dups, out, indent=2, ensure_ascii=False)
print(f"发现 {len(dups)} 组重复夹具候选,结果已写入 fixture-candidates.json")
关键指标:两阶段扫描后,重复夹具识别覆盖率 ≥ 85%,误报率 ≤ 10%,10万行代码库全量分析在 8 分钟内完成。
1.3 标记重复夹具候选
将扫描结果转化为可执行的候选清单,需要经过“分级 → 标注 → 评估 → 入库”四个步骤:
- 候选分级(P0–P3):P0 为完全复制,P1 为结构重复,P2 为组合重复,P3 为继承冗余。优先级按修复收益降序排列。
- 上下文标注:记录每个候选夹具所在的文件路径、起始行号、结束行号、所属测试类和方法名。
- 影响评估:估算消除重复后减少的代码行数、可合并的测试类数量以及预期的维护工时节省。
- 清单入库:将候选信息写入结构化 JSON 或 CSV 文件,供后续章节的夹具提取与重构环节消费。
常见误判场景:
- 仅变量名或字符串参数不同的结构相似代码 —— 应归入“参数化重复”,而非直接标记为冗余。
- 继承体系中基类方法被子类覆写但逻辑相同 —— 需结合类继承关系判断,避免将多态实现误标记为重复。
- 测试数据不同但操作步骤完全相同的流程 —— 应标记为“可参数化夹具”,保留数据变化维度。
第一阶段交付物:
- jscpd 全量扫描报告(jscpd-report.json)
- AST 精确过滤后的候选清单(fixture-candidates.json)
- 候选分级与影响评估摘要(fixture-candidates-summary.csv)