第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 文件,供后续章节的夹具提取与重构环节消费。
常见误判场景:
  • 仅变量名或字符串参数不同的结构相似代码 —— 应归入“参数化重复”,而非直接标记为冗余。
  • 继承体系中基类方法被子类覆写但逻辑相同 —— 需结合类继承关系判断,避免将多态实现误标记为重复。
  • 测试数据不同但操作步骤完全相同的流程 —— 应标记为“可参数化夹具”,保留数据变化维度。
识别重复夹具工作流程 测试代码库 模式分析 工具扫描 人工复核 标记重复 夹具候选 三阶段识别法:模式匹配 → 工具扫描 → 人工确认 覆盖完全复制、结构重复、组合重复、继承冗余四种模式,识别率 ≥ 85%
第一阶段交付物:
  • jscpd 全量扫描报告(jscpd-report.json)
  • AST 精确过滤后的候选清单(fixture-candidates.json)
  • 候选分级与影响评估摘要(fixture-candidates-summary.csv)