概述
功能开关不是配置细节,它意味着两套代码路径同时发布,其中一套对部分用户生效。如果测试只覆盖开启状态,关闭路径会悄悄腐化,第一次运行可能是在事故中——为了止血关掉开关,旧路径却因为另一个原因失败。两条路径都是生产代码,都需要覆盖。
步骤
- 让 handler 依赖一个只有一个方法的接口,而不是直接导入全局客户端:
export interface Flags {
isEnabled(key: string, context: FlagContext): boolean;
}
- 测试使用内存 Provider,显式声明 on/off 变体,避免访问远端开关服务。
- 参数化行为测试,两种状态都必须通过:
for (const enabled of [true, false]) {
test(`checkout works with checkout-v2=${enabled}`, async () => {
await OpenFeature.setProviderAndWait(providerFor(enabled));
assert.equal((await request('/api/checkout', { method: 'POST', body: cart })).status, 200);
});
}
- 断言缺失开关与未知开关返回调用方提供的安全默认值,且不抛异常。
- 为每个开关登记负责人与到期时间,定时任务发现过期即失败。
验证
关闭状态下旧实现应正常工作,开启状态下新实现正常运行,两者测试都通过。传入空 Provider 时 getBooleanValue('checkout-v2', false) 应返回 false 而不是抛错。把某个开关的到期时间设为昨天,定时检查应列出该开关并以非零状态退出。
注意事项
- 开关状态是全局的,测试之间容易泄漏,应在
beforeEach/afterEach重置或注入独立 client。 - 多开关组合会指数增长,应让每个开关独立测试、其余保持默认,只对真实共存的组合做端到端验证。
- 内存 Provider 不验证定向规则、用户分群与百分比灰度。
- 删除开关时同步删除死分支和只为灰度存在的测试,否则复杂度会持续累积。
- 到期检查属于流程控制,运行时不会阻止开关长期保留。