测试与收尾
实战:任务管理应用 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。
学习目标
项目收尾:给核心逻辑补测试、按验收清单自检、构建部署。这一节交付「可交付」的版本——别人 clone 下来能跑、关键行为有测试保护、部署后深链接可用。
问题场景
功能全做完了,npm run build 也过了。交付前三天改了一处排序逻辑,上线后发现「移到已完成」偶尔失效——没有测试,只能人工把每个按钮点一遍。项目交付的标准不是「我这儿能跑」,是「别人能跑 + 关键行为有保护」。
核心解释
测什么:按 V16 的原则,只测核心行为:
- store 的 action(纯逻辑,最好测):添加、移动、删除、排序。
- 组件的行为契约:卡片发出的
move事件参数正确;弹窗保存时发出 payload。 - 一条端到端主流程(可选):新建 → 移动 → 刷新保留(Playwright)。
store 测试不需要组件上下文:
// tests/tasks.spec.js
import { beforeEach, describe, expect, it } from "vitest";
import { createPinia, setActivePinia } from "pinia";
import { useTaskStore } from "../src/stores/tasks";
describe("task store", () => {
beforeEach(() => {
localStorage.clear();
setActivePinia(createPinia());
});
it("添加任务后进入待办列", () => {
const store = useTaskStore();
store.add("写 V18 测试");
expect(store.tasks).toHaveLength(1);
expect(store.byStatus.todo[0].title).toBe("写 V18 测试");
});
it("move 修改状态且幂等", () => {
const store = useTaskStore();
store.add("任务 A");
const id = store.tasks[0].id;
store.move(id, "doing");
expect(store.byStatus.doing).toHaveLength(1);
store.move(id, "doing");
expect(store.byStatus.doing).toHaveLength(1);
});
it("删除后列表与持久化同步", async () => {
const store = useTaskStore();
store.add("任务 B");
const id = store.tasks[0].id;
store.remove(id);
await Promise.resolve();
expect(store.tasks).toHaveLength(0);
});
});
注意 localStorage.clear():store 在创建时读 localStorage,测试之间必须清理,否则上一个测试的数据串进来。
组件契约测试:
// tests/TaskCard.spec.js
import { mount } from "@vue/test-utils";
import TaskCard from "../src/components/TaskCard.vue";
it("点击移列按钮发出 move 事件并携带目标状态", async () => {
const wrapper = mount(TaskCard, {
props: { task: { id: "t1", title: "任务", status: "todo", priority: "mid" } },
});
const buttons = wrapper.findAll("button");
const moveBtn = buttons.find((b) => b.text().includes("移到"));
await moveBtn.trigger("click");
expect(wrapper.emitted("move")[0]).toEqual(["t1", "doing"]);
});
交付验收清单(逐条打勾,缺一条都算没交付):
-
npm run dev与npm run build && npm run preview都能跑 - 新建 / 编辑 / 移列 / 删除 / 排序 / 筛选全部可用
- 刷新后数据保留;脏数据不崩
- 深链接
/task/:id直接打开正常 -
npm run test全绿;npm run lint无错 - 移动端(390px)三列正常堆叠、无横向滚动
- README 写清启动步骤、目录结构、已知限制
部署:按 V17 的流程构建,静态托管配回退规则,或用 Nginx 的 try_files。部署后重新过一遍验收清单里的「深链接」和「刷新」。
示例拆解
一个「收尾 commit」的检查脚本,把验收清单里可自动化的部分固化:
{
"scripts": {
"verify": "npm run typecheck && npm run test && npm run lint && npm run build",
"preview:check": "node scripts/check-preview.mjs"
}
}
// scripts/check-preview.mjs:构建后检查产物完整性
import fs from "node:fs";
const required = ["dist/index.html"];
for (const f of required) {
if (!fs.existsSync(f)) {
console.error(`缺少产物:${f}`);
process.exit(1);
}
}
const assets = fs.readdirSync("dist/assets");
if (!assets.some((f) => f.endsWith(".js"))) {
console.error("dist/assets 下没有 JS 产物");
process.exit(1);
}
console.log("产物检查通过");
verify 脚本是提交前的一键门禁,CI 里也跑同一条——本地和 CI 用同一标准,避免「我这儿过了」。
动手任务
- 给 store 的四个 action 写测试(添加/移动/删除/排序),全绿后再改一处实现,确认测试能拦住回归。
- 给 TaskCard 写 emit 契约测试。
- 按验收清单逐条自检,把结果写进 README 的「验收记录」一节;构建并部署,用真实域名复测深链接。
常见错误
- 测试之间不清理 localStorage:store 初始化读到上个测试的数据,断言随机失败。
- 只测 happy path:空标题、非法状态、删除不存在的 id 都没测,线上才炸。
- README 只写「npm install && npm run dev」:缺目录结构、缺已知限制,接手的人还是要读源码。
- 部署后不复测:本地 preview 和真实服务器的回退配置不同,深链接 404 只有线上能暴露。
检查与复盘
- store 四个 action 有测试且能拦住回归
- 验收清单逐条打勾并记录
- 部署后用真实域名验证深链接与刷新
参考资料
- Vitest 官方文档:测试组织与 mock
- Vue Test Utils 官方文档:组件契约测试
- Vue 官方文档「生产部署」:部署检查项
读完这一课了?
标记完成后,工作台会安排接下来的学习。