mcpField
D
dsh-ci-test-reliability

dsh-ci-test-reliability

github.com/deepseek-ai测试与质量264.8k39 次安装1 天前更新
代码仓库

关于 dsh-ci-test-reliability

用于设计能适应 CI 并发和平台差异的 DSH 测试技能,重点涵盖资源隔离、生命周期清理、状态同步及回归验证。

主要功能

  1. 01梳理重叠的 CI 执行拓扑
  2. 02原子分配端口和路径
  3. 03隔离并恢复全局状态
  4. 04依据可观测状态进行同步
  5. 05等待资源彻底释放
  6. 06验证回归并避免掩盖不稳定问题

适用场景

  1. 01避免并行测试进程间资源冲突
  2. 02可靠测试子进程和服务器清理
  3. 03诊断平台相关的 CI 测试故障

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/deepseek-ai/deepseek-harness --skill dsh-ci-test-reliability

安装后在 Claude Code 中输入/dsh-ci-test-reliability确认已加载。

附带文件references/ci-flake-diagnosis.md

详细介绍

可靠的 DSH CI 测试

构建的测试应在仓库实际的 CI 拓扑下保持正确,而不只是单独在安静的工作站上运行时正确。本技能负责隔离和可靠性决策;它不会取代仓库的测试分层策略,也不会为推送选择所有命令。

阅读适用规则

  • 使用测试策略选择单元测试、覆盖率、预期输出、快照、浏览器或真实 API 证据。
  • 使用防御性模式处理生命周期、子进程、取消和清理行为。
  • 当工作进程或作业拓扑会影响测试时,阅读当前生效的 Vitest 配置和 GitHub 工作流。
  • 对于录制会话场景,还应遵循快照说明。
  • 测试设计合理后,使用dsh-pre-push-checks选择待推送的验证项。

建模执行拓扑

除非当前生效的配置能够证明不会重叠,否则应假设以下各层可能同时运行:

  1. 同一个 Vitest 文件中的测试。
  2. 不同的 Vitest 文件或工作进程。
  3. 同一个作业中的独立 Vitest 进程或仓库门禁进程。
  4. Runner 共用一台主机的不同 Actions 作业。

进程隔离无法隔离主机端口、可预测的文件系统路径、外部服务、数据库、套接字或继承的子进程。对于每项已获取的资源,都要明确其所有者、原子分配机制、可观察的就绪信号、已注册的清理操作,以及表示操作已静止完成的信号。

不要仅仅因为某个测试夹具缺乏隔离,就串行执行整个测试套件。应先缩小独占范围或更改资源分配方式。Vitest 顺序执行代码块无法保护主机资源不受其他文件、进程、作业或 Runner 的影响。

原子分配资源

使用资源所有者提供的分配器,不要先检查资源是否可用,再稍后尝试占用。

  • 网络测试夹具使用 listen(0) 绑定回环地址,并且仅在服务器报告已监听后读取分配的地址。绝不要扫描空闲端口后再绑定。
  • 使用 mkdtemp 为每个测试创建私有临时根目录;不要占用可预测的共享路径。
  • 为共享数据库、套接字、会话和输出位置分配每个测试独有的命名空间。
  • 路径必须不存在时,使用独占创建。
  • 将稳定的录制标识符与临时传输地址分开。在测试夹具内部进行转换,而不是强制实时资源使用录制值。

仅用作解析器输入或预期值的字面路径和 URL 不属于已获取的资源。不要仅仅因为它们看起来是固定值就改写它们。

限制进程级全局状态

将 process.env、cwd、模拟计时器、区域设置和时区、模块模拟、注册表、控制台钩子、globalThis 以及对全局 fetch 的拦截视为独占的可变资源。

优先使用注入的依赖或实例级适配器。必须修改状态时:

  • 记录原始值是不存在还是存在;
  • 精确恢复到原始状态;
  • 立即注册恢复操作;
  • 在最小的修改范围内使用 try/finally;
  • 如果局部 finally 之前可能发生失败,则保留 afterEach 兜底;
  • 只拦截测试夹具所拥有的最精确请求或调用。

尊重平台自身的语义

CI 会在 Windows 和 POSIX 主机上运行同一套测试,而操作系统拥有的值不一定会以测试写入时的样子返回。

  • 只有当断言能够容忍回写失败时,回写才是安全的。将文件的 mtime 恢复原值,以证明指纹无论如何都会失效,这种做法在所有平台上都成立;但将它恢复原值以证明记录仍然有效,则假设了无损往返,而 NTFS 的 100 纳秒刻度无法精确保留小数毫秒。当断言依赖恢复结果时,应从新读取的值中获取预期值,而不是使用先前记住的值。
  • Windows 对环境变量名不区分大小写,因此在测试固件中将 http_proxy 和 HTTP_PROXY 作为两个独立键写入,在 Windows 上只会保留一项。
  • Windows 会异步释放文件句柄,因此在 POSIX 主机上能立即完成的重命名或删除操作,需要设置有上限的重试,时长应根据观察到的竞争情况确定。
  • Windows 不具备 POSIX 的权限或信号语义。依赖这些语义的测试用例应明确跳过相应平台并说明原因,而不是在所有平台上放宽断言。

优先采用在所有平台上都成立的观察方式。如果确实无法做到,应明确排除相应平台。

根据 CI 队列的预算设置超时

describe 或测试用例的超时会覆盖运行器的 --testTimeout,而不是服从它。因此,低于 CI 队列预算的值会缩短 CI 已分配的时限;同一个固定值在默认值更短的主机上,却会扩大时限。受进程创建约束的套件应采用 CI 队列预算;若使用更短的时限,必须说明原因。

提升测试预算时,也要提升钩子预算。设置和清理会承受同样的竞争压力,因此只提高测试用例预算,只会把竞争导致的失败转移到 afterEach。

当超时本身是测试对象时,外层等待时间应远大于被测超时。证明 20 ms 的截止时间会触发的测试用例,不应与测试框架自身的等待时间发生竞争,否则负载会决定哪个截止时间先报告。

根据状态进行同步

固定时长的睡眠不能证明设置已完成或清理已稳定。

  • 等待明确的就绪事件、握手、状态转换、由测试持有的 Promise,或可从外部观察到的条件。
  • 使用延迟 Promise 或屏障将竞态安排在确定的时点,并证明相关操作确实重叠。
  • 仅使用超时为等待设定上限,绝不能把超时作为断言成立的条件。
  • 除非调度顺序就是待测的产品行为,否则不要断言依赖调度器的执行顺序。
  • 当测试对象是时间本身时,应注入或模拟时钟,并始终恢复真实计时器。

清理至静止状态

获取资源后应立即注册清理,以确保断言失败时也能释放资源。清理应停止新的回调或请求、移除监听器、恢复全局钩子、终止所拥有的任务,并等待子进程退出、服务关闭、工作线程终止或等效的完成信号。

调用 abort()、close() 或 kill(),却不等待所拥有任务的完成信号,属于清理不完整。如果可能发生延迟完成,应证明释放资源后不会再修改其他测试。

证明预期的回归

  • 条件允许时,先观察修复前普通回归测试失败。
  • 对于新的静态检查或语料库检查,暂时引入被拒绝的情况,并观察预期的失败。
  • 对于竞态,使用屏障证明操作发生重叠;仅重复执行不算竞态测试。
  • 对于端口、套接字、共享路径、子进程或其他主机资源,如果修复涉及跨进程隔离,则应并发运行相互独立的测试进程。
  • 如果测试夹具启动的子进程有自己的截止时间,应先断言子进程未因信号或超时而结束,再断言其退出状态,以便被终止的子进程报告为超时,而非状态不匹配。
  • 验证外部状态、事件、文件、日志、退出状态或释放情况,而不是相信组件的自我报告。

压力测试可补充确定性回归测试,但不能替代它。

拒绝掩盖不稳定测试的修复

对于确定性的本地测试,不要将以下做法当作根因修复:

  • 未明确等待的状态就延长超时时间;
  • 添加重试;
  • 将所有文件改为串行执行;
  • 吞掉错误或未处理的拒绝;
  • 放宽断言;
  • 通过规范化消除不稳定行为;
  • 在清理或断言前添加休眠。

根据真实 API 策略,对于文档中说明的外部提供方瞬时故障测试,重试仍然有效。将这一例外限制在外部边界。

恢复预算不算掩盖问题。将测试套件的预算提高到其原本已有的通道预算,或将有界重试的规模设置为运行器上实际测得的争用程度,都明确指出了等待的工作,并恢复通道所提供的预算;两者都不会在未检查等待原因的情况下凭空增加余量。

诊断现有的不稳定测试

对于现有的概率性 CI 故障,请阅读 CI 不稳定测试诊断工作流。仅要求诊断时应保持只读:报告原因和证据,除非用户也要求修复。

验证并报告

针对受影响的行为运行范围最小的回归测试。仅当更改涉及以下风险时,才补充对应拓扑的证据:

  • 全局状态变更需要提供恢复证据;
  • 生命周期或子进程相关工作需要提供静默拆除的证据;
  • 端口、套接字或共享路径需要提供并发独立进程的证据;
  • 新增的检查需要提供负向对照。

推送前,请使用 dsh-pre-push-checks。准确报告命令和观察到的结果;不要将重试、跳过的测试或待处理的 CI 描述为通过。

评论

支持 Markdown