9.1 单元测试与端到端测试


9.1 单元测试与端到端测试

交付的第一道防线是测试网。本节讲两层:Vitest 单元测试罩住组合函数与组件(毫秒级反馈,定位精准),Playwright 端到端测试罩住真实浏览器里的用户路径(分钟级反馈,可信度高)。Nuxt 项目测试的特殊之处在于跨端逻辑——同一个函数有服务端与浏览器两条执行路径,测试策略要跟着分流。

单元层:测组合函数

先装环境(Nuxt 官方测试工具链):

npm install -D @nuxt/test-utils vitest
// nuxt.config.ts:注册测试环境模块 export default defineNuxtConfig({ modules: ['@nuxt/test-utils/module'], })

测试 4.3 的数据层组合函数。被测对象是"给定输入,输出与副作用是否符合预期":

// composables/useCart.test.ts import { mount } from '@vue/test-utils' // 1. 纯逻辑优先:总价计算不依赖渲染,抽出测试最划算 import { cartTotal } from '~/composables/cart-math' describe('购物车金额计算', () => { it('单件商品总价等于单价', () => { expect(cartTotal([{ price: 399, qty: 1 }])).toBe(399) }) it('多件商品按数量累加', () => { expect(cartTotal([ { price: 399, qty: 2 }, { price: 129, qty: 1 }, ])).toBe(927) }) it('空车为零且不报错', () => { expect(cartTotal([])).toBe(0) }) })

测组合函数里的跨端逻辑(useState 系列),需要 Nuxt 环境预设:

// composables/useToast.test.ts import { mountSuspended } from '@nuxt/test-utils/runtime' describe('useToast 提示队列', () => { it('show 后入队,超时后出队', async () => { vi.useFakeTimers() // 接管时钟(4.1 时间类坑的测试版) const wrapper = await mountSuspended({ template: '<div/>', setup() { const { toasts, show } = useToast() show('下单成功') return { toasts } }, }) expect(wrapper.vm.toasts).toHaveLength(1) vi.advanceTimersByTime(3000) // 快进三秒 expect(wrapper.vm.toasts).toHaveLength(0) }) })

mountSuspended 挂载的是带 Nuxt 运行时的组件——自动导入、useState、插件都真实可用,这比 mock 半天框架内部更可靠。测试策略上的取舍:纯函数抽出来直测(快而稳),框架依赖的用环境预设测(真而稍慢),UI 细节不强行单测(交给 E2E)

端到端层:测用户路径

单元测试证明零件没问题,端到端证明"用户真的能下单"。Playwright 驱动真实浏览器:

npm install -D @playwright/test npx playwright install chromium
// e2e/checkout.spec.ts import { test, expect } from '@playwright/test' test('游客浏览商品并完成登录下单', async ({ page }) => { // 1. 进首页:SSR 内容直接可读(这步同时在验证第3章的渲染) await page.goto('/') await expect(page.getByRole('heading', { level: 1 })).toContainText('接力商城') // 2. 进商品列表,点第一件商品 await page.getByRole('link', { name: /商品/ }).click() await page.locator('.product-card').first().click() await expect(page).toHaveURL(/\/products\/\d+/) // 3. 加购:角标数字变化(验证 4.2 的 store 联动) await page.getByRole('button', { name: /加入购物车/ }).click() await expect(page.locator('.badge')).toHaveText('1') // 4. 未登录结算被守卫拦下(验证 5.2 的中间件) await page.getByRole('link', { name: /结算/ }).click() await expect(page).toHaveURL(/\/login/) // 5. 登录后回跳完成下单 await page.fill('[name=email]', 'test@example.com') await page.fill('[name=password]', 'test1234') await page.getByRole('button', { name: /登录/ }).click() await expect(page).toHaveURL(/\/checkout/) })

这条测试串起了全册的核心机制:SSR 首屏、文件路由、状态联动、路由守卫、会话回跳——一次回归全链路。E2E 的写法纪律:选用户目标做断言,不断言实现细节(断言"角标显示 1"而不是"store.state.items.length === 1",后者是单元测试的事);选择器优先用角色与可访问名称(顺带保证 9.4 的无障碍属性真实存在)。

两层测试的分工与 CI 集成

图 9-2:两层测试网的覆盖模型

图 9-2:两层测试网的覆盖模型

维度 单元(Vitest) 端到端(Playwright)
测什么 函数、组合函数、组件逻辑 用户路径、页面协作
速度 毫秒级,可全量常跑 秒级,跑关键路径
失败定位 直指被测函数 需要排查链路
数量预期 多而细 少而关键

进 CI 的配置(第 9.3 节流水线的测试环节):

# .github/workflows/ci.yml 的测试段(平台任选,语法示意) jobs: test: runs-on: ubuntu-latest steps: - run: npm ci - run: npx vitest run # 单元:全量,必须过 - run: npm run build - run: npx playwright test # E2E:对生产构建跑,更接近线上

E2E 对构建产物而非 dev 服务器跑,能提前暴露"开发能跑、构建就崩"的问题(自动导入路径、服务端专属 API 误用这两类高频事故在这里被拦住)。

⚠️ 常见坑:E2E 测试依赖执行顺序与共享状态。测试 A 登录后测试 B 期望未登录状态,本地绿 CI 红。每条 E2E 用独立存储(新 context)或显式重置,测试之间零耦合。

💡 关键直觉:单元测试是"验零件",E2E 是"验交接"——Nuxt 的坑多数出在服务器与浏览器的接缝上(水合、状态、时序),这正是 E2E 的主场。两层都薄时,优先补 E2E 的关键路径。

本节要点回顾

  • 环境:@nuxt/test-utils 提供 Vitest 预设与 mountSuspended,框架能力真实可用免 mock;
  • 单测策略:纯函数抽出直测、框架依赖用预设测、UI 细节交给 E2E;
  • E2E 纪律:按用户目标断言、角色选择器优先、测试间零共享状态;
  • 一条关键路径胜过十条细节断言:登录下单链路同时回归全册核心机制;
  • CI 分层跑:单元全量、E2E 对构建产物,两类"开发正常构建崩"的事故提前拦截。

作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U