返回 AI Coding 培训
实操 06/11

用 FizzBuzz 学单元测试:验收实例、参数化测试与基础重构

4 分钟
AI编程AI工具效率方法

FizzBuzz 看起来像一道入门题,却非常适合练习真实开发中的三件事:需求会变化、规则会冲突、纯逻辑需要被可重复验证。这篇只聚焦单元测试和基础职责分离;浏览器 UI、后端 API 与遗留代码重构分别放在后续实操中展开。

这份练习使用 Vite 项目和 Vitest。Vitest 需要单独安装,并不是 Node.js 自带测试框架。

前置条件

  • Node.js 和包管理器可用。
  • 准备一个新的 Vite JavaScript 或 TypeScript 项目。
  • 已提交初始项目,方便比较每轮变化。

安装 Vitest:

pnpm add -D vitest

在 package.json 增加:

{
  "scripts": {
    "test": "vitest run"
  }
}

第一轮:建立基础规则

需求:

  • 3 的倍数输出 fizz。
  • 5 的倍数输出 buzz。
  • 同时满足时输出 fizz buzz。
  • 其他数字返回原数字。

先把需求写成实例:

输入输出
11
3fizz
5buzz
15fizz buzz

要求 AI 先实现纯函数,不要把计算逻辑直接写进页面事件。

第二轮:写参数化测试

import { describe, expect, it } from "vitest"
import { fizzBuzz } from "./fizz-buzz"

describe("fizzBuzz", () => {
  it.each([
    [1, "1"],
    [3, "fizz"],
    [5, "buzz"],
    [15, "fizz buzz"],
  ])("输入 %s 时返回 %s", (input, expected) => {
    expect(fizzBuzz(input)).toBe(expected)
  })
})

运行:

pnpm test

先看到测试通过,再进入下一轮。

第三轮:需求开始变化

新增规则:7 的倍数输出 with,多种倍数同时满足时保持 fizz buzz with 顺序。

不要先改实现,先补验收:7、21、35、105 分别应该输出什么?如果产品没有明确,就必须先提问。

把确认后的用例加入测试,先看到失败,再修改实现直到全部通过。

第四轮:加入高优先级包含规则

新增:数字中包含 3 时直接输出 base,优先级高于倍数规则。

关键实例包括:

  • 13 → base
  • 30 → base
  • 33 → base
  • 15 → fizz buzz

这一步会迫使我们明确“包含”和“倍数”的优先级,而不是让 AI 自己猜。

可以继续加入“包含 5 高于包含 7”等规则,但每次只增加一轮,并保留全部旧测试。

第五轮:把页面和逻辑分离

页面只负责:读取输入、调用 fizzBuzz、展示结果和错误提示。所有规则放在独立模块中。

如果当前代码把 HTML 操作、输入校验和规则判断写在同一个函数里,按下面顺序重构:

  1. 确认现有测试全部通过。
  2. 提取纯计算函数。
  3. 页面改为调用新函数。
  4. 再次运行单元测试。
  5. 浏览器手工验证一遍主流程。

测试保证行为,浏览器验证集成,两者缺一不可。

成功标准

  • 每次规则变化前先增加验收实例。
  • 测试能够先失败、实现后再通过。
  • 参数化数据清晰,没有复制大量断言。
  • 参数表中的每一行都能独立读懂,不用追踪循环状态。
  • 计算逻辑不依赖 DOM,可以被其他页面调用。
  • 页面输入非法值时有明确反馈。
  • 重构前后所有已确认行为一致。

常见问题与处理

AI 一次实现了所有未来规则

回退并按轮次开发。练习目标是体验变化与回归,不是最快得到最终答案。

测试跟着错误实现写

测试输入输出必须来自验收标准。先写预期,再让 AI 实现,避免它为自己的代码生成“证明自己正确”的测试。

Vitest 命令找不到

确认依赖已写入 devDependencies,使用项目包管理器运行脚本,不要把它误认为 Node 内置命令。

单测通过但页面失败

检查输入字符串到数字的转换、事件绑定和页面状态;再进入 Playwright UI 实操补一条关键行为冒烟测试,不要把所有页面问题都塞进单元测试。

课后练习

完成至少四轮规则变化,每轮单独提交 Git。最后查看提交历史,确认每一次都包含需求实例、测试和实现,而不是只有一份不可追踪的最终代码。

官方资料

AI编程AI工具效率方法