每年圣诞-新年航旅旺季,航空公司只有一条规则:系统不能出事。Virgin Atlantic 在这个硬deadline前完成了移动端 App 的大改版,单元测试覆盖率接近 100%,上线后 P1 缺陷为零。他们靠的不是加人加班,而是把 Codex 嵌进了开发流程的每个环节。
真正卡住的是什么
航旅 App 改版的技术难点不在功能本身——订票、选座、行李追踪,逻辑都成熟。卡点在:
- 测试债务堆积:老代码长期缺少单元测试,回归全靠手工,改一处怕牵一片。
- 时间不可协商:旺季流量窗口固定,代码必须在那之前 freeze,延期等于放弃收入。
- 人力瓶颈:团队规模不变,不可能在几周内凭人力补齐几百个测试用例。
Virgin Atlantic 的选择是:让 Codex 承担重复性最高的那部分工作——写测试。
Codex 在流程中的三个落点
根据公开信息,Virgin Atlantic 把 Codex 用在了三个具体位置,而不是泛泛地"让 AI 写代码":
1. 为既有业务逻辑补齐单元测试
开发人员先把核心模块的接口签名和业务规则用自然语言描述出来,Codex 根据描述生成测试骨架,人再补边界值和断言细节。覆盖率从个位数直接拉到接近满分——关键在于,Codex 生成的测试不是凑数的,它确实覆盖了正常路径和常见异常路径。
2. 生成 UI 组件的交互测试
移动端 App 有大量细碎的 UI 交互:按钮状态、表单校验、加载动画。这些测试写起来枯燥且易遗漏。Codex 根据组件 props 和设计规范批量生成测试,开发者只需确认交互意图是否被正确表达。
3. 辅助重构时的测试守卫
重构前,Codex 先为待改代码生成覆盖测试,确保重构不破坏既有行为。这相当于给重构加了一层安全网,团队才敢在旺季前动手改架构。
实践:用 AI 辅助补齐单元测试的工作流
下面给一个可直接改造的示例——用 OpenAI API 为一个 Python 模块批量生成单元测试。这和 Virgin Atlantic 用 Codex 补测试的思路一致,只是换成了你可以在自己项目里跑的版本。
假设你有一个订票模块 booking.py:
# booking.py — 航旅订票核心逻辑(简化示例)
class BookingService:
def __init__(self, inventory, pricing):
self.inventory = inventory
self.pricing = pricing
def book(self, flight_id, passenger_count, cabin_class):
"""订票:检查库存、计算价格、扣减座位"""
available = self.inventory.get_available(flight_id, cabin_class)
if available < passenger_count:
raise ValueError(f"座位不足: 需要 {passenger_count}, 可用 {available}")
price = self.pricing.calculate(flight_id, cabin_class, passenger_count)
self.inventory.decrease(flight_id, cabin_class, passenger_count)
return {"flight_id": flight_id, "passengers": passenger_count,
"cabin": cabin_class, "total_price": price}
用以下脚本调用 OpenAI API,为这个模块生成测试:
# generate_tests.py — 批量生成单元测试骨架
import openai
import inspect
def generate_tests(source_code: str, module_name: str) -> str:
"""调用 OpenAI 为给定源码生成 pytest 测试"""
prompt = f"""你是一位资深 Python 测试工程师。
请为以下模块编写完整的 pytest 单元测试,要求:
1. 覆盖正常路径和所有异常路径
2. 使用 mock 替换外部依赖(inventory, pricing)
3. 每个测试函数名清晰表达测试意图
4. 包含边界值测试
模块名: {module_name}
源码:
```python
{source_code}
```"""
response = openai.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": prompt}],
temperature=0.2, # 低温度保证输出稳定可复现
)
return response.choices[0].message.content
# ——— 主流程:读取源码 → 生成测试 → 写入文件 ———
if __name__ == "__main__":
from booking import BookingService
source = inspect.getsource(BookingService)
test_code = generate_tests(source, "booking")
# 提取代码块(去除 markdown 包裹)
lines = test_code.split("\n")
code_lines = []
in_block = False
for line in lines:
if line.strip().startswith("```python"):
in_block = True
continue
if line.strip() == "```":
in_block = False
continue
if in_block:
code_lines.append(line)
with open("test_booking_generated.py", "w") as f:
f.write("\n".join(code_lines))
print("测试已写入 test_booking_generated.py,请人工审核后运行 pytest")
运行前确保:
# 安装依赖
pip install openai pytest
# 设置 API Key
export OPENAI_API_KEY="sk-..."
# 执行生成
python generate_tests.py
# 审核后运行测试
pytest test_booking_generated.py -v
生成的测试大致会是这样的结构(需人工确认断言细节):
# test_booking_generated.py — AI 生成、人工审核后的测试
from unittest.mock import MagicMock
import pytest
from booking import BookingService
def test_book_success():
"""正常订票:库存充足,返回完整订单"""
inventory = MagicMock()
pricing = MagicMock()
inventory.get_available.return_value = 10
pricing.calculate.return_value = 5000.0
svc = BookingService(inventory, pricing)
result = svc.book("VS001", 2, "economy")
assert result["flight_id"] == "VS001"
assert result["passengers"] == 2
assert result["total_price"] == 5000.0
inventory.decrease.assert_called_once_with("VS001", "economy", 2)
def test_book_insufficient_seats():
"""异常路径:座位不足时抛 ValueError"""
inventory = MagicMock()
pricing = MagicMock()
inventory.get_available.return_value = 1 # 低于需求
svc = BookingService(inventory, pricing)
with pytest.raises(ValueError, match="座位不足"):
svc.book("VS001", 5, "economy")
# 确保未扣减库存
inventory.decrease.assert_not_called()
def test_book_boundary_exact_capacity():
"""边界值:恰好等于可用座位数"""
inventory = MagicMock()
pricing = MagicMock()
inventory.get_available.return_value = 3
pricing.calculate.return_value = 7500.0
svc = BookingService(inventory, pricing)
result = svc.book("VS001", 3, "economy")
assert result["passengers"] == 3
这个流程的核心不是"AI 替人写测试",而是AI 承担重复劳动,人负责审核意图和断言——和 Virgin Atlantic 的用法本质相同。
上线前的质量守卫清单
Virgin Atlantic 的结果(零 P1、覆盖率近满分)不是靠工具自动实现的,而是靠流程纪律。如果你打算在团队中引入类似做法,这份清单比工具本身更重要:
| 检查项 | 为什么重要 |
|---|---|
| AI 生成的测试是否覆盖了异常路径? | 正常路径容易覆盖,异常路径才是 P1 的来源 |
| 断言是否表达了真实业务意图? | AI 会写 assert result is not None,但你需要 assert result["status"] == "confirmed" |
| Mock 的行为是否和真实依赖一致? | Mock 返回硬编码值,上线后真实服务可能返回不同结构 |
| 是否有 AI 无法理解的隐式规则? | 航旅有大量业务惯例(舱位升级规则、退票窗口),AI 不一定知道 |
| 生成测试是否通过了 CI? | 必须跑在真实 CI 环境里,不能只在本地通过就合并 |
什么时候该用,什么时候不该用
Codex 这类工具在以下场景回报最高:
- 测试覆盖率是硬指标:合规要求、安全审计、或者像 Virgin Atlantic 这样旺季不能出事。
- 代码逻辑相对确定:业务规则清晰、接口稳定,AI 生成测试的准确率高。
- 团队有审核纪律:每个 AI 生成的测试都有人读过、改过、跑过。
以下场景要谨慎:
- 探索性代码:原型阶段逻辑频繁变动,AI 生成的测试跟不上节奏,反而增加维护负担。
- 高度领域特殊的逻辑:如果业务规则只有两三个老员工知道,AI 生成的测试大概率会遗漏关键约束。
- 安全敏感模块:支付、权限、加密——这些地方的测试必须由人从威胁模型出发编写,不能交给 AI 从代码表面推断。
Virgin Atlantic 的案例说明了一件事:在硬deadline前用 AI 补齐测试,不是偷懒,而是把人力从重复劳动中释放出来,集中到真正需要判断力的地方——业务意图的确认和边界条件的挖掘。工具是杠杆,支点还是人。