背景
很多人会在自定义 Step 里重写 PrePlanRun() 和 PostPlanRun() 做连接预热、缓存准备、状态回收。但在大计划里,绝大多数步骤并不会重写这两个方法。如果框架每次都无脑调用一遍空实现,累计开销并不小。OpenTAP 在这里做了一个很实用的优化:先判断“这个类型是否真的重写过”,再决定要不要进入 Pre/Post 生命周期,同时保证收尾顺序正确。
框架分析
核心思路有两层:
- 类型级缓存:
TestStep里用Dictionary<Type, bool>缓存“是否重写 Pre/Post”结果,避免反射重复计算。 - 实例级短路:每个步骤实例首次访问
PrePostPlanRunUsed时再取缓存,后续直接走布尔值。
执行阶段上,RunPrePlanRunMethods() 会在递归遍历步骤树时决定是否调用 PrePlanRun();结束阶段 finishTestPlanRun() 再按 StepsWithPrePlanRun 的逆序回放 PostPlanRun()。这就天然形成“先初始化的后释放”,和栈式资源管理一致。
实现过程
可以在本地直接复现这条链路:
1 | cd /home/ops/clawd/repos/opentap |
读代码时建议关注三个点:
Engine/TestStep.cs:preOrPostPlanRunOverridden(Type t)通过方法句柄比对判断是否重写,并写入静态字典。Engine/TestPlanExecution.cs:预执行阶段仅在PrePostPlanRunUsed == true时才真正调用step.PrePlanRun()。- 同文件收尾逻辑:
for (int i = run.StepsWithPrePlanRun.Count - 1; i >= 0; i--)逆序执行step.PostPlanRun(),避免资源释放顺序错乱。
注意事项
- 这个优化依赖“方法重写检测”,如果插件通过非常规方式注入行为(而非 override),可能不会触发预期路径。
StepsWithPrePlanRun记录的是已进入预执行链路的步骤,排查收尾遗漏时先看这个列表是否入栈。PostPlanRun()异常不会让流程再次崩掉,只会记录告警并继续收尾;插件开发应自行保证幂等和可重入。
小结
这段实现的价值在于:既减少了空生命周期调用的常态成本,又通过逆序回放把资源释放语义做对了。对包含大量“模板步骤”的测试计划,这种“先判断再调用”的快路径能明显降低无效开销,同时让 Pre/Post 的行为更可预测。
关键源码路径:
Engine/TestStep.csEngine/TestPlanExecution.csEngine/TestPlanRun.cs