内容提要
重构遗留代码前,应使用特征测试记录现有行为作为安全网。首先识别关键行为,选择最小测试边界,捕获返回值及副作用,并妥善处理数据库和外部依赖。可借助AI辅助发现行为,但需避免其臆造预期值。测试应聚焦外部可见行为而非实现细节,优先覆盖高风险逻辑。通过小步重构并持续运行测试,确保行为稳定,最终将特征测试转化为规范测试。
延伸解读
特征测试的核心价值
特征测试的核心价值在于记录系统当前的实际行为,而非预期行为。它不评判行为是否正确,而是将其作为重构的安全网。通过捕获返回值、副作用、外部调用等可观察行为,特征测试确保重构过程中行为不被意外改变。这种测试尤其适用于遗留代码,因为遗留代码往往包含许多未被文档化的隐含行为。
测试边界的选择策略
选择测试边界时,应寻找能提供足够信心的最小边界。这可能是函数、服务、模块或API端点。关键在于边界要能捕获有意义的行为,同时避免过度依赖实现细节。例如,服务级测试可能比单元测试更合适,因为它能覆盖协调多个内部调用的行为。
AI辅助的注意事项
AI可以加速特征测试的发现,但必须谨慎使用。AI可能根据代码推断出看似合理的预期值,但这些值可能并非实际行为。因此,应要求AI提供获取预期值的证据,而不是直接生成测试。同时,AI生成的测试应聚焦于可观察行为,避免冻结实现细节。
特征测试的局限性
特征测试只能证明所观察到的行为,无法解释行为背后的原因。例如,测试可以保护某个折扣规则,但无法说明该规则是出于税收、费用还是历史原因。因此,特征测试应与其他证据(如文档、Git历史、领域专家)结合,以全面理解系统。
Q&A
什么是特征测试(characterization test)?
特征测试是一种在重构遗留代码前,记录当前系统实际行为的测试。它不关注代码应该做什么,而是关注代码现在做什么,为后续重构提供安全网。
在重构遗留代码前,如何识别需要保护的关键行为?
首先选择一个业务能力(如生成发票、取消订单),然后追踪该能力在系统中的路径,识别出外部可见的行为,如返回值、状态变化、持久化效果、外部调用、事件、异常和边界条件。优先保护高风险、高业务影响的行为。
如何为遗留代码选择最小的测试边界?
选择最小的测试边界,即能捕获有意义行为的最小单元。通常不是最小的函数,而是服务或模块级别,既能保护行为又不过于复杂。例如,测试一个服务方法,而不是内部私有函数。
在特征测试中,如何处理数据库和外部依赖?
对于数据库,可以使用集成测试(使用一次性测试数据库)或引入一个接缝(seam)来隔离数据库访问。对于外部服务,通常使用模拟(mock)来捕获交互,但也要考虑合同测试和沙箱测试。
如何利用AI辅助生成特征测试,同时避免AI臆造预期值?
可以使用AI分析代码,识别可观察行为,但要求AI提供获取预期值的证据,而不是自行推断。例如,提示AI列出行为并说明如何从当前实现获取预期结果,避免AI根据常识猜测业务规则。
特征测试应该避免哪些陷阱?
避免测试实现细节,如内部调用顺序;避免让AI臆造预期值;避免试图覆盖所有行为,应优先高风险行为;避免将特征测试永久保留,应适时转化为规范测试。
在重构过程中,如何使用特征测试确保行为稳定?
在重构过程中,进行小步重构,每做一次结构变更就运行特征测试套件,确保行为没有变化。如果测试失败,可以快速定位问题。
特征测试能告诉我们什么,不能告诉我们什么?
特征测试能告诉我们系统当前的行为,但无法告诉我们行为背后的原因。例如,它可能显示某个调整存在,但无法解释为什么存在。需要结合文档、Git历史、领域专家等来理解。