写在前面

作为一名独立开发者,我最近大半年几乎每天都在和AI(Cursor、Copilot、Claude)一起写代码。有一个问题一直困扰着我:AI经常“理解错”我的意图

  • 我说“加一个用户登录功能”,AI自作主张加了邮箱验证、找回密码、记住我……
  • 我说“优化性能”,AI重构了整个架构,改了一堆我根本不想改的地方
  • 更糟糕的是,AI写了一大堆代码,跑起来全是Bug,我甚至不知道该从哪儿开始测试

直到我把TDD(测试驱动开发)BDD(行为驱动开发) 引入到AI开发流程中,一切都变了。

这篇文章,我会分享独立开发者如何在AI时代,用TDD和BDD让AI严格按照你的意图实现功能。全是实战经验,没有理论空话。


一、基础知识:TDD和BDD到底是什么?

TDD(Test-Driven Development 测试驱动开发)—— 让代码逻辑正确

一句话概括先写测试,再写代码

红-绿-重构循环

  1. :先写一个会失败的测试(因为功能还没实现)
  2. 绿:写最少的代码让测试通过
  3. 重构:优化代码,确保测试依然通过

核心价值:保证代码逻辑正确,每个函数/方法都在做它该做的事。

典型的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 流程

  1. 切换到单元测试文件(如 utils/dateHelper.test.ts
  2. 使用 Vitest 为该函数编写 TDD 测试(先写失败测试 → 实现 → 重构)
  3. 确保单元测试通过后,再回到 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%的核心逻辑用这个

我的个人建议

  1. 从BDD开始:先想清楚功能边界,让AI把边界写成可执行测试
  2. 用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红-绿-重构循环,每一步都问我是否继续"

# 修复失败的测试
"测试失败了,错误信息:[粘贴]。请修复代码让测试通过。"

# 补充边界测试
"现有测试通过了,请补充[具体场景]的边界测试"

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐