OpenTAP 在执行测试计划前,会递归调用所有启用步骤的 PrePlanRun() 方法。官方文档只提到“做预准备”,却鲜少提及:当计划含上千步骤且嵌套层级深时,这一阶段可能成为启动瓶颈。今天从源码角度拆解其执行链路,并给出可落地的提速方案。
背景:启动慢,别只怪仪器连接
不少开发者把“测试启动卡几秒”归咎于仪器 Open(),却忽视 PrePlanRun 的串行递归。引擎主函数 ExecTestPlan 在真正跑步骤前,会:
- 调用
RunPrePlanRunMethods递归遍历所有启用节点; - 对每一步包裹
ResourceManager.BeginStep/EndStep; - 默认串行,且单线程内完成。
若单步 PrePlanRun 平均 5 ms,1000 步就是 5 s,且无法通过并行仪器连接隐藏。
框架分析:递归与资源令牌
核心代码在 Engine/TestPlanExecution.cs:
1 | static bool RunPrePlanRunMethods(IList<ITestStep> steps, TestPlanRun planRun) |
PrePostPlanRunUsed是TestStep的 internal bool,默认 true;BeginStep/EndStep会拿资源锁,即使步骤本身并不访问仪器;- 递归返回 false 即短路,任何异常都会中止整个计划。
实现过程:把“无实际预操作”的步骤标记为跳过
若自定义步骤无需 PrePlanRun,可在构造函数关闭标记:
1 | [] |
对于无状态、不占用硬件的步骤(计算、判读、延迟等),此举可把耗时从 O(n) 降到 0。
注意事项
- 仅当步骤确定不依赖
PrePlanRun()内初始化资源、不抛出异常时再关闭; - 关闭后
PlanRun属性在PrePlanRun阶段不再注入,若代码后期重构依赖该属性会 NPE; - 官方内置的
DelayStep、RepeatStep已默认关闭,可作为参考; - 若步骤需要动态启用/禁用 PrePlanRun,可在
OnEnabledChanged里同步修改PrePostPlanRunUsed。
小结
PrePlanRun 的设计让“准备动作”有了统一钩子,却也带来串行开销。通过合理使用 PrePostPlanRunUsed = false,可把启动时间从秒级降到毫秒级,尤其对大批量无状态步骤场景效果显著。下次遇到“计划启动慢”,先 profile 一下 PrePlanRun,再决定要不要并行开仪器,别一味加线程。
可复现验证
1 | # 创建 500 个空步骤并测量启动耗时 |
关键源码路径
Engine/TestPlanExecution.cs:RunPrePlanRunMethods、ExecTestPlanEngine/TestStep.cs:PrePostPlanRunUsed 属性定义Engine/ResourceManager.cs:BeginStep/EndStep 资源锁实现