AI开发时代下,独立开发者的TDD/BDD实战指南:如何让AI精准按你的意图写代码
写在前面
作为一名独立开发者,我最近大半年几乎每天都在和AI(Cursor、Copilot、Claude)一起写代码。有一个问题一直困扰着我:AI经常“理解错”我的意图。
- 我说“加一个用户登录功能”,AI自作主张加了邮箱验证、找回密码、记住我……
- 我说“优化性能”,AI重构了整个架构,改了一堆我根本不想改的地方
- 更糟糕的是,AI写了一大堆代码,跑起来全是Bug,我甚至不知道该从哪儿开始测试
直到我把TDD(测试驱动开发) 和BDD(行为驱动开发) 引入到AI开发流程中,一切都变了。
这篇文章,我会分享独立开发者如何在AI时代,用TDD和BDD让AI严格按照你的意图实现功能。全是实战经验,没有理论空话。
一、基础知识:TDD和BDD到底是什么?
TDD(Test-Driven Development 测试驱动开发)—— 让代码逻辑正确
一句话概括:先写测试,再写代码。
红-绿-重构循环:
- 红:先写一个会失败的测试(因为功能还没实现)
- 绿:写最少的代码让测试通过
- 重构:优化代码,确保测试依然通过
核心价值:保证代码逻辑正确,每个函数/方法都在做它该做的事。
典型的TDD测试:
Python (pytest)
def test_calculate_total_price():
cart = ShoppingCart()
cart.add_item("book", price=100, quantity=2)
total = cart.calculate_total()
assert total == 200 # 2本书,每本100元
Node.js/TypeScript (Jest/Vitest)
test('calculateTotalPrice', () => {
const cart = new ShoppingCart();
cart.addItem('book', 100, 2);
const total = cart.calculateTotal();
expect(total).toBe(200);
});
这种测试程序员看的,产品经理看不懂。
BDD(Behavior-Driven Development 行为驱动开发)—— 让功能符合业务预期
一句话概括:先描述行为,再实现代码。
核心工具:Gherkin语法(Given-When-Then)
Feature: 购物车结算
Scenario: 购买两本书
Given 购物车是空的
When 我添加2本单价100元的书
Then 总价应该是200元
核心价值:保证功能符合业务期望,非技术人(你作为产品经理的角色)也能看懂。
关键区别:
- TDD问:函数逻辑对吗?(技术视角)
- BDD问:功能对吗?(业务视角)
独立开发者的最佳姿势:BDD定方向 + TDD扣细节
先用BDD描述功能 → 确认这就是你要的 → 再用TDD一步步实现
这样,AI就不会“跑偏”了。
二、你的技术栈速查表
| 场景 | Python | Node.js/TypeScript | Vue 3 |
|---|---|---|---|
| 单元测试 | pytest | Vitest / Jest | Vitest + Vue Test Utils |
| BDD/E2E测试 | pytest-bdd / behave | Cypress / Playwright | Cypress / Playwright |
| Mock/Stub | unittest.mock | vi.mock (Vitest) / jest.mock | 同Node.js |
| 测试命令 | pytest |
npm run test |
npm run test:e2e |
本文所有示例都会用这三种技术栈分别展示。
三、AI开发的痛点:为什么AI总是不按你的意图来?
典型场景
你的需求(在你脑子里):
“加一个简单的注册功能,只要邮箱+密码就行”
AI的理解(在AI的模型里):
“用户注册应该包括:邮箱验证、密码强度检查、验证码、记住我、第三方登录、忘记密码……”
结果:AI生成了500行代码,你删掉400行,还要修100行的Bug。
根本原因
AI没有业务上下文,它只能根据训练数据中的“常见模式”猜测。你给的提示越模糊,AI猜得越离谱。
解决方案:用测试作为“契约”
不用测试时:
你:实现用户注册
AI:……(疯狂发挥)
用测试时:
你:实现用户注册。这是我写的BDD场景和TDD测试,请让它们通过。
AI:好的,我只用实现这些被测试要求的功能,不会多写一行冗余代码。
测试就是可执行的合同,AI必须严格遵守。
四、实战方法一:BDD先行 + AI辅助(Vue 3 + Cypress 示例)
适用场景
- 开发新功能,需求还不完全确定
- 你需要先想清楚功能边界
- 你希望AI能“精准”实现你的想法
核心流程(4步)
第1步:用中文写BDD场景 → 让AI转成Given-When-Then
你先用大白话写(不要管格式):
“我想做一个任务管理功能:用户可以添加任务,任务默认是‘未完成’状态。添加后能看到任务列表。”
让AI转成Gherkin格式:
请把上面的需求转成Gherkin格式(Feature/Scenario/Given-When-Then)
AI输出:
Feature: 任务管理
Scenario: 添加一个新任务
Given 我是登录用户
When 我添加一个任务,内容为"买牛奶"
Then 任务列表中应该显示"买牛奶"
And 任务状态应该是"未完成"
关键点:你验证这个Gherkin是否准确描述了你的意图。如果不对,改中文需求,重新让AI转。
第2步:让AI生成BDD自动化测试代码(Vue 3 + Cypress)
⚠️ 重要区分:本步骤属于 BDD(行为驱动开发),不是 TDD。
- BDD测试(本步骤):验证整个功能行为是否符合业务预期,模拟真实用户操作,使用 Cypress / Playwright / Cucumber 等工具。
- TDD测试:验证单个函数/模块的逻辑正确性,使用 Vitest / pytest / JUnit 等单元测试框架。
两者是互补关系:BDD 负责宏观行为验证,TDD 负责微观逻辑保障。本方法主要演示 BDD 流程。
你的提示词:
基于上面的Gherkin场景,为我的Vue 3应用生成Cypress测试(BDD风格)。
使用describe/it格式,使用data-testid选择器。
AI输出(BDD风格的自动化测试代码):
// cypress/e2e/task-management.cy.ts
describe('任务管理 (BDD场景验证)', () => {
beforeEach(() => {
cy.visit('/tasks')
})
it('Scenario: 添加一个新任务', () => {
// Given 我是登录用户 —— 已通过 beforeEach 中的访问实现
// When 我添加一个任务,内容为"买牛奶"
cy.get('[data-testid="task-input"]').type('买牛奶')
cy.get('[data-testid="add-button"]').click()
// Then 任务列表中应该显示"买牛奶"
cy.contains('买牛奶').should('be.visible')
// And 任务状态应该是"未完成"
cy.contains('买牛奶')
.parents('[data-testid="task-item"]')
.contains('未完成')
.should('be.visible')
})
})
这段代码直接映射了 Gherkin 中的 Given-When-Then 行为,属于 BDD 层面的自动化测试。它模拟了真实用户操作(输入、点击、检查页面元素),测试的是完整的功能行为,而不是一个独立的函数。这符合 BDD 的核心价值:让测试可读、可协作、面向业务场景。
(可选)补充:如果在实现中遇到复杂逻辑,再引入 TDD
如果在实现 Vue 组件的过程中,某个函数(如日期格式化、权限校验)逻辑复杂且容易出错,可以在此嵌入 TDD 流程:
- 切换到单元测试文件(如
utils/dateHelper.test.ts) - 使用 Vitest 为该函数编写 TDD 测试(先写失败测试 → 实现 → 重构)
- 确保单元测试通过后,再回到 BDD 流程运行 Cypress 整体验证
示例提示词:
在刚才的任务管理组件中,需要一个“计算任务过期时间”的辅助函数。
请切换至 TDD 模式:先用 Vitest 为这个函数编写单元测试(覆盖正常/边界/异常情况),
然后实现函数代码。完成后再回到 BDD 的 Cypress 测试。
这样,你就拥有了 BDD 驱动宏观行为 + TDD 保障微观逻辑 的完整 AI 协作流程。
五、实战方法二:TDD驱动 + AI结对编程
适用场景
- 实现具体函数/算法(比如数据校验、计算逻辑)
- 重构现有代码
- 你很清楚功能逻辑,只是不想手写代码
核心流程(5步)
第1步:告诉AI你要实现什么,并要求它先写测试
提示词模板:
我要实现一个[函数名],功能是[功能描述]。
请用TDD方式:
1. 先用[测试框架名]写一组失败测试(覆盖正常/边界/异常情况)
2. 我先确认测试是否正确
3. 然后你再写实现代码
实战例子1 - Python:
我要实现一个validate_password函数,功能是:
- 长度至少8位
- 包含至少1个数字
- 包含至少1个大写字母
请用pytest写TDD测试。
AI输出:
# test_password_validator.py
import pytest
from password_validator import validate_password
def test_valid_password():
assert validate_password("Abc123456") == True
def test_too_short():
assert validate_password("Ab1") == False
def test_no_digit():
assert validate_password("Abcdefghi") == False
def test_no_uppercase():
assert validate_password("abc123456") == False
def test_empty_string():
assert validate_password("") == False
实战例子2 - TypeScript (Vitest):
我要实现一个validatePassword函数,功能同上。请用Vitest写TDD测试。
AI输出:
// passwordValidator.test.ts
import { describe, expect, it } from 'vitest'
import { validatePassword } from './passwordValidator'
describe('validatePassword', () => {
it('should accept valid password', () => {
expect(validatePassword('Abc123456')).toBe(true)
})
it('should reject too short password', () => {
expect(validatePassword('Ab1')).toBe(false)
})
it('should reject password without digit', () => {
expect(validatePassword('Abcdefghi')).toBe(false)
})
it('should reject password without uppercase', () => {
expect(validatePassword('abc123456')).toBe(false)
})
it('should reject empty string', () => {
expect(validatePassword('')).toBe(false)
})
})
第2步:你确认测试覆盖全面
检查AI的测试是否遗漏了场景:
- 如果有遗漏:告诉AI"再补充测试:密码全是数字的情况"等
- 如果满意:继续下一步
第3步:让AI写最简单的实现(只求测试通过)
测试我已经确认过了,现在请写最简单的代码实现,只求让这些测试通过,不要过度设计。
Python实现:
# password_validator.py
def validate_password(password: str) -> bool:
if len(password) < 8:
return False
if not any(c.isdigit() for c in password):
return False
if not any(c.isupper() for c in password):
return False
return True
TypeScript实现:
// passwordValidator.ts
export function validatePassword(password: string): boolean {
if (password.length < 8) return false
if (!/[0-9]/.test(password)) return false
if (!/[A-Z]/.test(password)) return false
return true
}
第4步:运行测试验证
# Python
pytest test_password_validator.py -v
# Node.js/TypeScript
npm run test
全部通过 ✅
第5步(可选):让AI重构
测试都通过了。现在请重构代码,让它更简洁/高效,保持测试通过。
AI可能会改成:
Python重构版:
def validate_password(password: str) -> bool:
return (
len(password) >= 8 and
any(c.isdigit() for c in password) and
any(c.isupper() for c in password)
)
TypeScript重构版:
export const validatePassword = (password: string): boolean =>
password.length >= 8 &&
/[0-9]/.test(password) &&
/[A-Z]/.test(password)
Vue 3 组件的TDD示例
测试Vue组件使用 Vitest + Vue Test Utils:
我要实现一个Counter组件,功能:
- 显示初始值0
- 点击+1按钮,数值+1
- 点击-1按钮,数值-1
- 数值不会小于0
请用Vitest + Vue Test Utils写TDD测试。
AI输出:
// Counter.spec.ts
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import Counter from './Counter.vue'
describe('Counter', () => {
it('displays initial value 0', () => {
const wrapper = mount(Counter)
expect(wrapper.text()).toContain('0')
})
it('increments when +1 button is clicked', async () => {
const wrapper = mount(Counter)
await wrapper.find('[data-testid="increment"]').trigger('click')
expect(wrapper.text()).toContain('1')
})
it('decrements when -1 button is clicked', async () => {
const wrapper = mount(Counter)
await wrapper.find('[data-testid="decrement"]').trigger('click')
expect(wrapper.text()).toContain('-1')
})
it('never goes below 0', async () => {
const wrapper = mount(Counter)
// 先减到0
await wrapper.find('[data-testid="decrement"]').trigger('click')
await wrapper.find('[data-testid="decrement"]').trigger('click')
expect(wrapper.text()).toContain('0')
})
})
然后让AI实现Counter.vue组件。
六、两种方法的对比与选择
| 维度 | BDD先行法 | TDD驱动法 |
|---|---|---|
| 最适合 | 新功能开发、UI/交互逻辑 | 工具函数、算法、数据验证 |
| 需求清晰度 | 需求还不完全确定 | 需求已经很明确 |
| AI的角色 | 写场景 → 写测试 → 实现 | 写测试 → 实现 → 重构 |
| 产出可读性 | 产品/业务都能看懂 | 只有程序员能看懂 |
| 调试速度 | 功能级出错,容易定位 | 单元级出错,精确定位 |
| 推荐比例 | 80%的功能开发用这个 | 20%的核心逻辑用这个 |
我的个人建议:
- 从BDD开始:先想清楚功能边界,让AI把边界写成可执行测试
- 用TDD细化:BDD测试覆盖整个功能后,里面的复杂逻辑用TDD单独测试
七、完整工作流模板(可直接复制使用)
模板1:Vue 3 + Cypress(前端功能开发)
我要开发一个新功能:[功能名称]
请帮我按以下流程走:
第1步(BDD):
- 请根据我的需求写出Gherkin场景(Given-When-Then)
- 我会确认或修改
第2步(Cypress测试):
- 基于确认的Gherkin,生成Cypress测试代码
- 使用data-testid选择器
第3步(Vue 3组件):
- 实现Vue 3 Composition API组件
- 确保Cypress测试通过
需求描述:[粘贴你的需求]
模板2:Python/Node.js 后端API开发
我要开发一个新API:[API名称和功能]
第1步(BDD):
- 写Gherkin场景描述API行为
第2步(集成测试):
- [Python] 用FastAPI TestClient / pytest
- [Node.js] 用Supertest / Vitest
- 写会失败的API测试
第3步(实现):
- 实现API端点,让测试通过
需求描述:[粘贴你的需求]
模板3:纯函数/算法(TDD)
我要实现一个[函数名],功能是[功能描述]。
技术栈:[Python / TypeScript]
请用TDD方式:
1. 先用[pytest / Vitest]写一组失败测试(覆盖正常/边界/异常情况)
2. 我先确认测试是否正确
3. 然后你再写实现代码
4. 最后帮我重构
需求描述:[粘贴你的需求]
八、实战案例:做一个待办事项后端API(完整流程)
技术栈:Python FastAPI + pytest + 假设要集成Vue 3前端
第1步:BDD场景定义
你的中文需求:
做一个待办事项API:可以创建事项、获取所有事项、标记事项为完成。
AI转成Gherkin:
Feature: 待办事项API
Scenario: 创建新事项
Given 事项列表为空
When 我创建一个事项"写文章"
Then 事项列表包含"写文章"
And 状态为"未完成"
Scenario: 标记事项为完成
Given 存在一个事项"写文章"
When 我标记"写文章"为完成
Then "写文章"的状态变为"完成"
第2步:生成API测试(pytest + FastAPI TestClient)
基于上面的Gherkin,为FastAPI生成pytest集成测试。
使用TestClient,测试/todos端点。
AI输出:
# test_todos.py
from fastapi.testclient import TestClient
from main import app
client = TestClient(app)
def test_create_todo():
# When 我创建一个事项"写文章"
response = client.post("/todos", json={"content": "写文章"})
assert response.status_code == 200
# Then 事项列表包含"写文章"
get_response = client.get("/todos")
todos = get_response.json()
assert any(todo["content"] == "写文章" for todo in todos)
def test_mark_todo_complete():
# Given 存在一个事项
client.post("/todos", json={"content": "写文章"})
# When 标记为完成
response = client.put("/todos/0/complete")
assert response.status_code == 200
# Then 状态变为完成
get_response = client.get("/todos")
todo = get_response.json()[0]
assert todo["completed"] == True
第3步:实现API
请实现FastAPI代码,让上面的测试通过。
使用Python,内存存储(list),暂时不用数据库。
AI输出:
# main.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List
app = FastAPI()
class TodoCreate(BaseModel):
content: str
class Todo(BaseModel):
id: int
content: str
completed: bool = False
todos: List[Todo] = []
next_id = 0
@app.post("/todos")
def create_todo(todo: TodoCreate):
global next_id
new_todo = Todo(id=next_id, content=todo.content)
todos.append(new_todo)
next_id += 1
return new_todo
@app.get("/todos")
def get_todos():
return todos
@app.put("/todos/{todo_id}/complete")
def complete_todo(todo_id: int):
for todo in todos:
if todo.id == todo_id:
todo.completed = True
return todo
raise HTTPException(status_code=404, detail="Todo not found")
第4步:测试通过 ✅
pytest test_todos.py -v
# 全部通过
九、进阶技巧:Vue 3 + TypeScript 项目的完整测试配置
安装依赖
# Vue 3项目
npm create vue@latest my-app
cd my-app
# 单元测试(Vitest + Vue Test Utils)
npm install -D vitest @vue/test-utils jsdom
# E2E测试(Cypress)
npm install -D cypress
配置vitest.config.ts
import { defineConfig } from 'vitest/config'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
test: {
environment: 'jsdom',
globals: true,
},
})
package.json添加脚本
{
"scripts": {
"test": "vitest",
"test:ui": "vitest --ui",
"test:e2e": "cypress run",
"test:e2e:open": "cypress open"
}
}
十、常见陷阱与避坑指南
❌ 陷阱1:让AI写测试但不验证
坏习惯:
AI生成测试 → 直接让AI写代码 → 测试通过 → 觉得安心了
问题:AI可能写了假的测试(永远通过的测试)或者遗漏场景的测试。
正确做法:
- 先看AI写的测试,确认逻辑正确
- 思考:“有没有我没说但应该测的场景?”
- 补充测试给AI
- 再让AI实现
❌ 陷阱2:AI写了实现,但你不理解
坏习惯:
AI写了复杂的实现 → 测试通过了 → 代码合并 → 两周后出Bug,你不知道怎么修
正确做法:
如果AI的实现你看不懂,要求它:
“请用更简单的方式重新实现,保持测试通过。
如果必须用复杂逻辑,请解释每段代码的作用。”
❌ 陷阱3:测试依赖外部服务
坏习惯:
测试直接调用真实数据库/API
正确做法:
# Python - 使用mock
from unittest.mock import Mock, patch
@patch('external_api.call')
def test_with_mock(mock_call):
mock_call.return_value = {"status": "ok"}
# 测试代码
// TypeScript - 使用vi.mock
vi.mock('axios', () => ({
default: {
get: vi.fn().mockResolvedValue({ data: {} })
}
}))
十一、收益总结:独立开发者必须用TDD/BDD的理由
1. AI的输出质量大幅提升
- 不用测试:AI产出符合预期的概率约30%
- 用BDD:上升至60%
- BDD + TDD:上升至90%以上
2. 你不用手工验证每一个细节
传统AI开发流程:
AI写代码 → 你手动测试 → 发现Bug → 告诉AI修复 → 再手动测试 → 循环
加入测试后:
AI写代码 + 测试 → 运行测试 → 测试失败了 → AI根据失败信息修复 → 测试通过 → 完工
节省时间:至少减少70%的手工验证工作。
3. 代码长期可维护
没有测试的AI生成代码 = 技术债务原子弹
- 3个月后,你自己都看不懂AI写了什么
- 改一行代码,不知道会破坏什么
- 不敢重构
有测试的AI生成代码 = 可靠的资产
- 跑一遍测试,马上知道改动的影响
- 放心重构
- 代码价值持续积累
写在最后
独立开发者最大的优势是快,但最大的风险也是快——赶得太快,代码质量崩塌,最后被Bug淹没。
AI + TDD/BDD不是“多出来的步骤”,而是让你更快更准的工具:
- BDD帮你想清楚要什么
- TDD帮你验证AI做得对不对
- AI帮你快速实现
三者结合,你会发现AI不再是“一个猜你心思的助手”,而是一个精确执行你意图的结对编程搭档。
从下一个功能开始,试试BDD先行吧。
附录:快速参考
技术栈对应表
| 用途 | Python | Node.js/TypeScript | Vue 3 |
|---|---|---|---|
| 单元测试框架 | pytest | Vitest | Vitest |
| BDD/E2E | pytest-bdd + requests | Playwright / Cypress | Cypress |
| Mock | unittest.mock | vi.mock / jest.mock | 同Node.js |
| 断言风格 | assert x == y |
expect(x).toBe(y) |
同Node.js |
常用测试命令
# Python
pytest # 运行所有测试
pytest -v # 详细输出
pytest -k "test_name" # 运行匹配的测试
pytest --cov=. # 测试覆盖率
# Node.js/Vitest
npm run test # 运行测试
npm run test -- --watch # 监听模式
npm run test -- --coverage # 覆盖率
# Vue 3 E2E (Cypress)
npx cypress open # 打开交互界面
npx cypress run # 命令行运行
AI提示词速查
# BDD场景生成
"根据我的需求生成Gherkin场景:[需求描述]"
# 生成测试
"基于上面的Gherkin,生成[框架名]测试代码"
# TDD测试先行
"我要实现[函数名],请先用[测试框架]写TDD测试"
# 红绿重构
"请模拟TDD红-绿-重构循环,每一步都问我是否继续"
# 修复失败的测试
"测试失败了,错误信息:[粘贴]。请修复代码让测试通过。"
# 补充边界测试
"现有测试通过了,请补充[具体场景]的边界测试"
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐
所有评论(0)