当一个已经可以运行的 Vue 3 项目临近上线,却还缺少单元测试、端到端测试和覆盖率报告时,从零补齐质量保障体系往往十分耗时。Codex CLI 可以直接在终端中读取项目、生成测试并执行验证。下面将从安装配置开始,逐步完成 Vitest、Cypress 与覆盖率相关任务。
上周我给一个Vue3任务管理项目加测试,用Codex CLI写了40个单元测试和12个E2E测试。整个过程花了不到2小时,其中1小时在等网络请求。
你的Vue3任务管理应用已经跑起来了,有任务列表、状态切换、拖拽排序。现在老板说:“下周上线前得有测试覆盖率报告。”你打开package.json,里面连vitest都没装。测试从零开始,而你只有两天时间。
这正好是Codex CLI的主战场。你只需要在终端里输入需求,它就会自己读代码、写测试、跑测试、修复问题。
先确认Node.js版本。Codex CLI需要18.0.0或更高。
node --version
# 应该显示 v18.x.x 或更高
安装Codex CLI。两种方式任选其一:
# npm方式(推荐)
npm install -g @openai/codex
# macOS用户可以用Homebrew
brew install codex
验证安装:
codex --version
# 应该显示版本号,比如 0.36.0
接下来是认证。Codex CLI支持两种认证方式。
方式一:ChatGPT账号(推荐)
codex
# 首次运行会自动弹出登录提示
# 用你的ChatGPT Plus/Pro账号登录
登录成功后会看到Authenticated字样。这种方式最简单,订阅用户直接用,不需要额外付费。
方式二:API Key
适合需要精确控制成本的场景:
export OPENAI_API_KEY="sk-your-key-here"
把这行加到~/.zshrc或~/.bashrc里,这样每次打开终端都会自动设置。
Codex CLI的配置文件在~/.codex/config.toml。首次运行会自动创建,你也可以手动创建。
创建一个基础配置:
mkdir -p ~/.codex
touch ~/.codex/config.toml
写入一个适合日常开发的配置:
# 模型选择
model = "o4-mini" # 性价比最高的选项
# 审批策略
approval_policy = "on-request" # 需要确认时会问你
sandbox_mode = "workspace-write" # 只能在项目目录内写文件
# 界面设置
hide_agent_reasoning = false # 显示思考过程,方便调试
file_opener = "cursor" # 点击文件路径时用Cursor打开
# 隐私设置
disable_response_storage = true # 不存储对话数据
关键配置项解释:
模型选择:o4-mini是性价比最高的选项,速度快,成本低。如果你的任务很复杂,可以换o3,但要注意成本会高很多。
审批策略:on-request是日常开发的最佳选择。Codex会在需要写文件或执行命令前问你,但不会每一步都问。suggest模式太保守,full-auto太危险,不建议在正式项目用。
沙箱模式:workspace-write确保Codex只能修改你项目目录内的文件,不会动系统文件。这是安全底线。
响应存储:设为true可以保护你的代码隐私,特别是商业项目。
保存配置后,重启终端让配置生效。
现在用Codex CLI完成前端测试与质量保证的五个任务。
启动Codex,指向你的项目目录:
cd ~/projects/task-manager
codex
在Codex里输入:
为src/components/TaskList.vue编写Vitest单元测试。
要求:
1. 测试组件渲染任务列表
2. 测试点击任务时触发select事件
3. 测试空列表时显示提示信息
4. 使用@vue/test-utils
5. 创建__tests__/TaskList.test.ts文件
Codex会自己读取TaskList.vue的代码,理解组件结构,然后生成测试文件。它会自动安装必要的依赖(如果还没装的话)。
生成的测试文件大概长这样:
// src/components/__tests__/TaskList.test.ts
import { describe, it, expect, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import TaskList from '../TaskList.vue'
const mockTasks = [
{ id: 1, title: '测试任务1', completed: false },
{ id: 2, title: '测试任务2', completed: true }
]
describe('TaskList', () => {
it('renders task list correctly', () => {
const wrapper = mount(TaskList, {
props: { tasks: mockTasks }
})
expect(wrapper.findAll('.task-item')).toHaveLength(2)
expect(wrapper.text()).toContain('测试任务1')
})
it('emits select event when task is clicked', async () => {
const wrapper = mount(TaskList, {
props: { tasks: mockTasks }
})
await wrapper.find('.task-item').trigger('click')
expect(wrapper.emitted('select')).toBeTruthy()
expect(wrapper.emitted('select')[0]).toEqual([mockTasks[0]])
})
it('shows empty state when no tasks', () => {
const wrapper = mount(TaskList, {
props: { tasks: [] }
})
expect(wrapper.text()).toContain('暂无任务')
})
})
运行测试验证:
npx vitest run
如果测试通过,你会看到类似这样的输出:
✓ src/components/__tests__/TaskList.test.ts (3 tests) 12ms
✓ TaskList > renders task list correctly
✓ TaskList > emits select event when task is clicked
✓ TaskList > shows empty state when no tasks
Test Files 1 passed (1)
Tests 3 passed (3)
Start at 10:30:45
Duration 234ms
如果测试失败,Codex会自动分析错误原因并尝试修复。你可以直接告诉它:"测试失败了,帮我看看什么原因",它会读取错误日志并调整代码。
继续在Codex里输入:
为任务管理功能编写Cypress E2E测试。
测试场景:
1. 访问首页,看到任务列表
2. 点击"新建任务"按钮,填写表单,提交
3. 新任务出现在列表中
4. 点击任务状态切换按钮,状态改变
5. 创建cypress/e2e/task-management.cy.ts
Codex会生成一个完整的E2E测试文件。它还会帮你配置Cypress(如果还没配置的话)。
生成的测试文件示例:
// cypress/e2e/task-management.cy.ts
describe('Task Management', () => {
beforeEach(() => {
cy.visit('/')
})
it('displays task list on homepage', () => {
cy.get('.task-list').should('be.visible')
cy.get('.task-item').should('have.length.greaterThan', 0)
})
it('creates a new task', () => {
const taskTitle = `测试任务 ${Date.now()}`
cy.get('[data-testid="new-task-button"]').click()
cy.get('input[name="title"]').type(taskTitle)
cy.get('textarea[name="description"]').type('这是测试描述')
cy.get('button[type="submit"]').click()
cy.get('.task-item').should('contain', taskTitle)
})
it('toggles task status', () => {
cy.get('.task-item').first().within(() => {
cy.get('[data-testid="status-toggle"]').click()
})
cy.get('.task-item').first().should('have.class', 'completed')
})
})
Codex还会帮你配置cypress.config.ts:
// cypress.config.ts
import { defineConfig } from 'cypress'
export default defineConfig({
e2e: {
baseUrl: 'http://localhost:5173',
specPattern: 'cypress/e2e/**/*.cy.{js,ts}',
supportFile: 'cypress/support/e2e.ts',
viewportWidth: 1280,
viewportHeight: 720,
video: false,
screenshotOnRunFailure: true
}
})
运行E2E测试:
# 打开Cypress测试运行器
npx cypress open
# 或者无头模式运行(适合CI)
npx cypress run
在无头模式下,Cypress会生成视频和截图,方便你排查问题。如果测试失败,你会看到具体的错误信息和失败位置。
在Codex里输入:
配置Vitest的测试覆盖率。
要求:
1. 使用v8作为覆盖率提供器
2. 覆盖率报告包含text、html、lcov格式
3. 覆盖率阈值:语句80%,分支75%,函数80%,行80%
4. 配置在vitest.config.ts中
Codex会修改你的vitest配置文件:
// vitest.config.ts
import { defineConfig } from 'vitest/config'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
test: {
globals: true,
environment: 'jsdom',
coverage: {
provider: 'v8',
reporter: ['text', 'html', 'lcov'],
thresholds: {
statements: 80,
branches: 75,
functions: 80,
lines: 80
}
}
}
})
在package.json里添加覆盖率脚本:
{
"scripts": {
"test:unit": "vitest run",
"test:coverage": "vitest run --coverage",
"test:e2e": "cypress run"
}
}
运行覆盖率报告:
npm run test:coverage
你会看到终端输出覆盖率统计:
% Coverage report from v8
-------------------------|---------|----------|---------|---------|
File | Stmts | Branch | Funcs | Lines |
-------------------------|---------|----------|---------|---------|
All files | 82.35% | 76.47% | 81.25% | 82.35% |
src/components | 85.71% | 80.00% | 83.33% | 85.71% |
TaskList.vue | 90.00% | 85.71% | 100.00% | 90.00% |
TaskItem.vue | 80.00% | 75.00% | 75.00% | 80.00% |
src/stores | 78.57% | 72.22% | 80.00% | 78.57% |
taskStore.ts | 78.57% | 72.22% | 80.00% | 78.57% |
-------------------------|---------|----------|---------|---------|
同时会在coverage/目录下生成HTML报告,用浏览器打开coverage/index.html可以查看详细的覆盖率信息,精确到每一行代码。
告诉Codex你要用测试驱动开发:
我要用TDD方式开发一个任务过滤功能。
需求:支持按"全部"、"已完成"、"未完成"三种状态过滤任务。
请按TDD流程:
1. 先写失败的测试
2. 写最少的代码让测试通过
3. 重构
每一步都给我看具体代码。
Codex会引导你完成TDD的红-绿-蓝循环。它会先创建测试文件:
// src/composables/__tests__/useTaskFilter.test.ts
import { describe, it, expect } from 'vitest'
import { useTaskFilter } from '../useTaskFilter'
const mockTasks = [
{ id: 1, title: '任务1', completed: false },
{ id: 2, title: '任务2', completed: true },
{ id: 3, title: '任务3', completed: false }
]
describe('useTaskFilter', () => {
it('filters all tasks by default', () => {
const { filteredTasks, activeFilter } = useTaskFilter(mockTasks)
expect(activeFilter.value).toBe('all')
expect(filteredTasks.value).toHaveLength(3)
})
it('filters completed tasks', () => {
const { filteredTasks, setFilter } = useTaskFilter(mockTasks)
setFilter('completed')
expect(filteredTasks.value).toHaveLength(1)
expect(filteredTasks.value[0].completed).toBe(true)
})
it('filters active tasks', () => {
const { filteredTasks, setFilter } = useTaskFilter(mockTasks)
setFilter('active')
expect(filteredTasks.value).toHaveLength(2)
expect(filteredTasks.value.every(t => !t.completed)).toBe(true)
})
})
然后创建最小化的实现代码:
// src/composables/useTaskFilter.ts
import { ref, computed } from 'vue'
type FilterType = 'all' | 'active' | 'completed'
export function useTaskFilter(tasks: any[]) {
const activeFilter = ref<FilterType>('all')
const filteredTasks = computed(() => {
switch (activeFilter.value) {
case 'completed':
return tasks.filter(t => t.completed)
case 'active':
return tasks.filter(t => !t.completed)
default:
return tasks
}
})
const setFilter = (filter: FilterType) => {
activeFilter.value = filter
}
return { filteredTasks, activeFilter, setFilter }
}
最后它会建议重构方案,比如把FilterType提取到单独的类型文件里,或者添加更多的过滤选项。
最后让Codex配置代码质量检查:
为项目配置ESLint。
要求:
1. 支持Vue3 + TypeScript
2. 集成Prettier
3. 配置strict规则集
4. 添加scripts到package.json
Codex会安装必要的包并生成配置文件。它通常会推荐使用eslint-config-prettier来避免ESLint和Prettier冲突。
生成的ESLint配置文件:
// eslint.config.js
import js from '@eslint/js'
import pluginVue from 'eslint-plugin-vue'
import tseslint from 'typescript-eslint'
import prettier from 'eslint-config-prettier'
export default tseslint.config(
js.configs.recommended,
...tseslint.configs.recommended,
...pluginVue.configs['flat/recommended'],
prettier,
{
files: ['**/*.{ts,vue}'],
rules: {
'vue/multi-word-component-names': 'off',
'@typescript-eslint/no-unused-vars': 'warn',
'@typescript-eslint/explicit-function-return-type': 'off'
}
}
)
在package.json里添加脚本:
{
"scripts": {
"lint": "eslint . --fix",
"lint:check": "eslint ."
}
}
检查配置是否正确:
npx eslint --print-config src/main.ts
这个命令会输出当前文件使用的ESLint规则,帮你确认配置是否生效。
问题1:网络请求超时
国内网络连接OpenAI API不稳定。Codex CLI每次操作都要请求API,网络问题会导致超时。
解决方案:设置代理环境变量。
export HTTPS_PROXY=http://127.0.0.1:7890
export HTTP_PROXY=http://127.0.0.1:7890
把这两行加到你的shell配置文件里。
问题2:沙箱阻止操作
你可能看到错误:Sandbox denied operation。这是因为Codex想执行一些不在沙箱白名单里的命令。
解决方案:临时切换到更宽松的沙箱模式。
codex --sandbox workspace-write
# 或者完全绕过(仅在容器里用)
codex --dangerously-bypass-approvals-and-sandbox
问题3:配置不生效
修改了config.toml但没效果。Codex CLI在启动时读取配置,运行中不会重新加载。
解决方案:退出Codex重新启动,或者用命令行参数临时覆盖。
CODEX_MODEL=o4-mini codex
问题4:测试文件找不到
Codex生成了测试文件,但Vitest找不到。可能是文件路径不对,或者配置没更新。
解决方案:检查vitest.config.ts的include配置:
test: {
include: ['src/**/*.{test,spec}.{js,ts}']
}
问题5:Cypress找不到元素
E2E测试里用cy.get('.button')找不到元素。可能是因为Vue组件渲染的DOM结构和预期不同。
解决方案:用data-testid属性而不是CSS类。
<button data-testid="submit-button">提交</button>
cy.get('[data-testid="submit-button"]').click()
Codex CLI是OpenAI生态里的终端编程代理,特别适合需要快速原型开发和测试的场景。
配置清单:
npm install -g @openai/codex~/.codex/config.toml速查表:
| 功能 | 命令 |
|---|---|
| 启动Codex | codex |
| 指定模型 | codex --model o3 |
| 全自动模式 | codex --full-auto |
| 查看配置 | codex --help |
| 重新登录 | codex logout && codex |
与其他工具的对比:
| 工具 | 特点 | 适合场景 |
|---|---|---|
| Cursor | IDE集成,图形界面 | 日常编码,需要可视化反馈 |
| Claude Code | 终端工具,配置复杂 | 需要多Agent协作,复杂任务 |
| Codex CLI | 终端工具,OpenAI生态 | 快速原型,测试编写,GPT用户 |
Codex CLI的核心优势是轻量和快速。它不需要打开IDE,直接在终端里就能完成大部分任务。特别是对于测试编写这种重复性工作,Codex CLI的效率很高。
如果你主要使用OpenAI的模型,Codex CLI是最自然的选择。它和ChatGPT订阅深度集成,登录就能用,不需要额外配置API Key。
下一篇预告:我们换到 OpenCode,用它配置前端部署和坚控。四个工具都过一遍后,最后一篇谈谈怎么把它们组合成一套完整的工作流。