ChatGPT充值后,很多开发者会使用 Codex 为项目补充单元测试、接口测试和回归用例。

测试刚生成时可能全部通过,但连续运行几次后,却出现一些难以解释的现象:

  • 同一段代码没有修改,测试结果却不一样;

  • 单独运行某个用例可以通过,执行全部测试时失败;

  • 本地测试正常,提交到 CI 后偶尔报错;

  • 调整用例顺序后,原本失败的测试又恢复正常;

  • 白天运行正常,跨天或跨时区后开始失败;

  • 测试依赖真实接口,网络波动时随机失败;

  • 为了让构建通过,只能反复重新运行测试。

这类测试通常被称为 Flaky Test,也就是不稳定测试。

它最大的问题不是“偶尔报错”,而是会逐渐降低团队对测试结果的信任。当开发者习惯通过重跑解决失败后,真正的代码缺陷也可能被当成偶发问题忽略。

一、为什么同一个测试会产生不同结果?

一条可靠的自动化测试,应该满足一个基本条件:

输入条件相同,执行结果也应保持一致。

如果同一测试在相同代码下得到不同结果,通常说明它依赖了不稳定因素,例如:

  • 当前时间;

  • 随机数;

  • 网络请求;

  • 数据库历史数据;

  • 用例执行顺序;

  • 全局变量;

  • 共享缓存;

  • 异步任务完成时间;

  • 操作系统和时区;

  • 其他测试留下的状态。

Codex 可以快速生成测试代码,但如果任务中只要求“补充测试”,它可能更关注测试能否覆盖函数,而没有同时处理这些外部依赖。

二、测试不要直接依赖当前时间

下面这种测试很容易在日期变化时失败:

it("should create today's report", () => {
  const report = createReport();

  expect(report.date).toBe(
    new Date().toISOString().slice(0, 10)
  );
});

如果代码执行刚好跨过零点,或者 CI 使用的时区与本地不同,测试结果就可能发生变化。

更稳定的方式,是把时间作为参数传入:

function createReport(now = new Date()) {
  return {
    date: now.toISOString().slice(0, 10)
  };
}

测试中使用固定时间:

it("should create report for fixed date", () => {
  const now = new Date(
    "2026-08-04T09:00:00+08:00"
  );

  const report = createReport(now);

  expect(report.date).toBe("2026-08-04");
});

这样,测试结果不会受到执行时间影响。

让 Codex 编写与时间相关的测试时,可以明确要求:

不要直接依赖系统当前时间。

请使用固定时钟或可注入时间,并覆盖:

1. 正常日期;
2. 跨天;
3. 月末;
4. 闰年;
5. 不同时区。

三、随机数据必须固定种子

部分测试会使用随机数据:

const userId = Math.floor(
  Math.random() * 100000
);

这种方式看似可以增加测试覆盖,但一旦失败,很难复现当时的输入。

更合理的方法是:

  • 使用固定测试数据;

  • 为随机生成器设置固定种子;

  • 失败时记录实际输入;

  • 将随机测试与普通单元测试分开。

例如:

const user = {
  id: 1001,
  name: "test-user",
  status: "active"
};

如果确实需要随机测试,应保证失败后可以根据种子重新运行。

四、每个测试都要清理自己的数据

测试之间共享数据库,是 Flaky Test 的常见来源。

例如,第一个测试创建了用户:

test@example.com

但执行结束后没有删除。

第二个测试再次创建相同邮箱时,就可能因为唯一约束失败。

稳定的测试应该做到:

测试开始前准备数据
→ 执行测试
→ 验证结果
→ 清理本轮数据

可以在每个用例前后执行:

beforeEach(async () => {
  await resetTestDatabase();
});

afterEach(async () => {
  await clearTestCache();
});

对于数据库集成测试,也可以使用事务:

  1. 测试开始时开启事务;

  2. 在事务中执行数据修改;

  3. 测试结束后统一回滚。

这样能够避免测试数据污染后续用例。

五、测试不能依赖执行顺序

下面两条测试可能单独运行都正常:

测试一:创建用户
测试二:查询用户

但如果测试二依赖测试一创建的数据,那么调整执行顺序后就会失败。

每个测试都应该独立准备自己需要的数据。

不稳定写法:

let userId;

it("creates user", async () => {
  const user = await createUser();
  userId = user.id;
});

it("loads user", async () => {
  const user = await getUser(userId);
  expect(user).toBeDefined();
});

更稳定的方式是,在第二个测试内部创建用户:

it("loads user", async () => {
  const created = await createUser();
  const user = await getUser(created.id);

  expect(user).toBeDefined();
});

测试虽然会多执行一次准备工作,但独立性更强。

六、不要在单元测试中调用真实外部接口

如果测试直接请求短信、支付、地图或其他第三方服务,就会受到以下因素影响:

  • 网络延迟;

  • 服务限流;

  • 测试账号状态;

  • 接口维护;

  • 地区网络差异;

  • 第三方返回数据变化;

  • 调用额度不足。

普通单元测试应该使用 Mock:

const paymentClient = {
  createPayment: jest.fn().mockResolvedValue({
    status: "success",
    transactionId: "test-001"
  })
};

需要验证真实接口时,可以单独建立集成测试,并设置清晰标签,避免每次普通提交都调用外部服务。

测试层级可以分为:

  • 单元测试:不访问真实网络;

  • 集成测试:验证模块之间协作;

  • 端到端测试:模拟完整用户流程;

  • 外部接口测试:按需单独运行。

不要把所有验证都塞进同一层测试。

七、异步测试不要使用固定等待时间

下面这种写法不稳定:

startTask();

await sleep(1000);

expect(task.status).toBe("completed");

本地电脑可能一秒内完成,但 CI 资源较少时,任务可能需要更长时间。

如果把等待时间改成五秒,测试会变慢,而且仍然不能保证稳定。

更合理的方式是等待明确条件:

await waitFor(() => {
  expect(task.status).toBe("completed");
});

或者让异步函数返回 Promise:

const result = await startTask();

expect(result.status).toBe("completed");

测试应该等待实际事件完成,而不是猜测任务需要多久。

八、缓存和全局变量也会污染测试

测试运行时,模块缓存、单例对象和全局状态可能在不同用例之间保留。

例如:

let currentUser = null;

第一个测试修改了 currentUser,第二个测试可能读取到旧值。

需要在每个测试前重置:

beforeEach(() => {
  currentUser = null;
});

如果项目使用 Redis、本地缓存或内存缓存,也要在测试后清理对应键。

尤其是并行执行测试时,共享状态更容易产生冲突。

九、CI与本地环境需要保持一致

本地通过、CI偶尔失败,可能与以下环境差异有关:

  • Node.js或Python版本不同;

  • 时区不同;

  • CPU和内存资源不同;

  • 测试并发数不同;

  • 数据库版本不同;

  • 环境变量缺失;

  • 操作系统文件名大小写规则不同。

建议固定:

  • 运行时版本;

  • 包管理器;

  • 依赖锁文件;

  • 测试数据库版本;

  • 默认时区;

  • 测试并发参数。

还可以在 CI 日志中输出非敏感环境信息:

Node.js:20.x
Timezone:UTC
Database:PostgreSQL 16
Test workers:4

这样更容易判断失败是否来自环境差异。

十、不要用自动重跑掩盖问题

部分 CI 配置会在测试失败后自动重跑。

重跑可以临时降低偶发故障对发布流程的影响,但不能成为长期解决方案。

如果一条测试第一次失败、第二次通过,应该记录:

  • 哪条测试失败;

  • 首次错误是什么;

  • 重跑次数;

  • 是否属于已知 Flaky Test;

  • 最近失败频率;

  • 负责人和修复计划。

不建议无限重跑,或者一直提高重跑次数。

否则构建虽然变绿了,测试本身仍然不可靠。

十一、为不稳定测试建立隔离区

如果某个测试暂时无法立即修复,可以先进行隔离:

正常测试套件
Flaky测试套件
外部接口测试套件
性能测试套件

隔离不是永久忽略,而是避免它持续影响主流程,同时保留修复任务。

每条被隔离的测试都应该记录:

  • 失败原因;

  • 出现频率;

  • 是否影响核心业务;

  • 修复负责人;

  • 预计恢复时间。

不要直接删除不稳定测试,因为它可能仍然覆盖重要业务风险。

十二、把测试稳定性规则写入AGENTS.md

长期项目可以加入:

# 测试稳定性规则

- 单元测试不得依赖真实外部接口
- 与时间相关的逻辑必须使用固定时钟
- 随机测试必须使用固定种子
- 每个测试独立准备和清理数据
- 测试不能依赖执行顺序
- 禁止使用固定sleep等待异步结果
- 测试结束后清理缓存和全局状态
- CI失败不能只通过重复运行解决
- 修复Bug时必须增加稳定的回归测试
- Flaky Test必须记录原因和修复计划

这样,Codex在生成测试时,会把“是否稳定”作为完成条件之一。

十三、让Codex输出测试稳定性报告

任务完成后,可以要求:

本轮新增测试:

- 用户创建测试
- 重复邮箱测试
- 接口超时测试

稳定性处理:

- 使用固定测试时间
- 未调用真实外部接口
- 每条测试独立创建数据
- 测试结束后清理数据库
- 未使用固定sleep
- 已验证随机顺序执行

仍需观察:

- CI并行执行时的数据库连接数

这比只回答“新增了8条测试,全部通过”更有参考价值。

十四、Plus适合哪些测试任务?

如果主要使用Codex完成以下工作,Plus通常可以满足多数需求:

  • 为单个模块补充测试;

  • 修复时间相关用例;

  • 增加Mock;

  • 清理测试数据;

  • 排查单条Flaky Test;

  • 建立基础测试规则。

这些任务通常可以按照一个文件或一个模块拆分完成。

十五、哪些情况可以评估Pro?

如果长期工作包含以下场景,可以根据实际强度评估Pro:

  • 每天维护大量自动化测试;

  • 一个问题涉及代码、数据库和CI;

  • 需要连续分析多次失败日志;

  • 同时维护多个测试环境;

  • 大型仓库测试执行时间较长;

  • Codex已经参与主要测试和交付流程;

  • 当前使用空间经常影响完整排查。

对于大型测试套件、多环境和需要持续分析的工程场景,Pro更适合高频、长任务工作流。

但更高的使用方案不能让不稳定测试自动变可靠。真正解决问题的仍然是时间隔离、数据清理、固定输入和环境一致性。

总结

ChatGPT充值后,Codex生成的测试一会通过、一会失败,通常不是测试框架随机出错,而是用例依赖了时间、网络、执行顺序或共享数据。

通过固定时间与随机种子、隔离外部服务、独立准备测试数据、清理缓存和避免固定等待,可以显著降低Flaky Test。

对于单模块和普通测试任务,Plus通常已经够用。对于大型测试套件、多环境CI和需要连续分析失败日志的高频工程场景,Pro更适合复杂工作流。

真正可靠的自动化测试,不只是某一次运行显示绿色,而是在不同时间、不同机器和不同执行顺序下,都能得到相同结果。

CSDN文章描述

本文介绍ChatGPT充值后使用Codex时,如何通过固定时间、随机种子、Mock、测试数据隔离和CI环境统一,解决测试偶尔通过、偶尔失败的Flaky Test问题,并分析ChatGPT Plus与Pro的适用场景。

推荐标签

ChatGPT充值
ChatGPT
Codex
单元测试
ChatGPT Plus

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐