跳转到主要内容

测试

运行单元、集成与浏览器端到端测试

项目为源码配备了单元、集成与端到端三层自动化测试,本节介绍各层测试的内容组织与运行方式。

运行测试

测试代码位于 test/,运行时使用 bun 内置的 bun:test。前端组件与组合式函数的用例依赖 happy-dom 模拟的浏览器环境,这类测试专用包与 Playwright 一起安装在独立的 test/package.json 中,模式与 scripts/ 子包相同,首次运行前先安装依赖:

Bashbun run test:e2e:install

在项目根目录执行:

Bashbun run test

单元与集成测试的文件名以 .test.ts 结尾。测试内部通过 #shared、#server 与 #test 别名导入被测代码和公共替身,对应关系在 test/tsconfig.json 中维护。

只运行部分测试时,在命令后追加文件路径或名称片段:

Bashbun run test shared

追加 -t "用例名" 可以按用例名称继续过滤。需要覆盖率报告时执行:

Bashbun run test --coverage

测试分层

unit/ 是单元测试,其下 app/、server/、shared/ 三个目录与源码路径一一对应,例如 server/api/comments.get.ts 的用例放在 test/unit/server/api/comments.get.test.ts,新增用例时按被测文件的路径放置即可。app/ 侧覆盖组件、组合式函数、指令、插件与工具函数,server/ 侧覆盖接口、中间件、路由、插件与服务端工具,shared/ 覆盖两个 bundle 共用的纯函数。unit/real/ 直接运行真实实现、不使用 mock,验证码、Prisma 与 IP 归属地库的用例都在这里。

integration/ 是集成测试,使用 .env 里 DB_* 配置的真实 PostgreSQL 数据库。db/ 直接调用 Prisma Client,验证表约束、外键级联、事务与关系查询,覆盖内容、评论、更新日志、订阅、旅行地点、用户与会话存储等数据表,也包含大数据量分页的性能用例;real/ 走真实的接口 handler 流程,以管理员身份请求后台接口验证完整的增删改查,_setup.ts 默认在 ${DB_NAME}_test 旁路库建表(TEST_DB_NAME 可覆盖),_helpers.ts 的 resetDb() 每次测试前 TRUNCATE CASCADE 并重新 seed 管理员。集成测试执行时会清空业务表数据,仅保留初始化的管理员账户,因此主库与旁路库都要保持「开发库」语义、不要在生产库上跑。

helpers/ 是各层共用的测试替身,包括 Prisma 假件、登录态假件和 Nitro 自动导入的运行时替身;types/ 存放测试类型声明;zz-late/ 里的用例按目录名排在最后执行,作为 mock 泄漏的兜底检查。

浏览器端到端测试

端到端测试使用 Playwright,代码位于 test/e2e/,文件名以 .spec.ts 结尾,bun run test 不会收集,两套测试互不影响。用例按目录分组:admin/ 覆盖后台的文章编辑与保存、分类、标签、评论管理、友链审批、订阅、旅行地点、附件上传、系统设置与账户资料;user-actions/ 覆盖前台的登录、发表与回复评论、留言、搜索、友链申请和主题切换;根目录的 public-pages.spec.ts、api-smoke.spec.ts 与 not-found.spec.ts 分别检查公开页面渲染、公开接口可达性和 404 页面。

E2E 的依赖已随上一节的 test:e2e:install 一起装好,还需要安装 Chromium 浏览器:

Bashbun run --cwd test e2e:browsers

执行端到端测试:

Bashbun run test:e2e

测试默认连接 localhost:3001 上的 dev 服务,该端口已有服务时直接复用,没有时以同一端口自动启动 bun run dev,端口可以通过 E2E_PORT 环境变量修改。用到登录态的用例以固定凭据 admin / 123456 登录,用户名和密码写死在 test/e2e/_admin.ts 中,没有环境变量可以覆盖;开发库的管理员密码修改过之后,这批用例会全部登录失败,跑之前需要先执行 bun run reset:password 按提示把密码重置回 123456。登录是单端的,会踢掉现有后台会话。用例串行执行,CI 环境下失败自动重试两次并保留 trace 与失败截图。

本地调试可以执行 bun run --cwd test e2e:ui 使用 Playwright 的图形界面挑选和观察用例,执行完成后 bun run --cwd test e2e:report 打开 HTML 报告。

第三方服务冒烟

冒烟用例位于 test/e2e/smoke/,常规的 bun run test:e2e 不会执行这组用例,发版前手动执行:

Bashbun run test:e2e:smoke

用例对五项第三方配置做只读探活:COS 检查存储桶域名可达,返回 404 说明桶名或地域配置有误;高德经站点的 /_AMapService 代理验证 key 有效;百度内容审核用后台保存的凭证换取访问令牌;SMTP 连接后台配置的主机并等待 220 横幅;GitHub API 检查限流余量。对应服务未配置的用例自动跳过;百度与 SMTP 两项需要登录后台读取凭证,同样受上面固定凭据的约束。

注意事项

开发库凭据被改过可走环境变量覆盖。test/e2e/_admin.ts 的默认凭据仍是 init-db 种子的 admin / 123456,但 loginAdmin() 现在按 process.env.E2E_ADMIN_USER / E2E_ADMIN_PASS 覆盖,凭据变更过的开发库可以这样跑:先 E2E_ADMIN_USER=... E2E_ADMIN_PASS=... bun run test:e2e,或把这个 env 写进 shell profile。无环境变量时行为不变,仍然依赖 init-db 种子。reset:password 脚本只改密码、不改用户名;只改过密码的环境设 E2E_ADMIN_PASS 后可以直接跑,只改过用户名的环境还要先回滚用户名。

E2E 不会自动恢复,登录是单端的。loginAdmin 内部登录时会踢掉当前浏览器上下文外的后台会话,并立即失败导致同一次登录后的 401;如果测试过程中途异常退出,可能留下一个被踢但未恢复的会话状态。开两步验证的账户 loginAdmin 探测到 totpRequired 后会返回 false 并触发 ADMIN_SKIP_MSG 跳过用例,不会真的去解 TOTP。这是为了不在测试里自动完成二次认证,避免误把测试用例的密码塞进任何 2FA 通道。需要 2FA 关掉才能跑大多数 E2E。

dev 服务端口固定为 3001,本地已开 3001 或想换端口时会改路径。Playwright 配置把 baseURL 与 webServer 都锁在 localhost:3001(playwright.config.ts 第 5 行注释明确写「约定 localhost:3001」),并通过 reuseExistingServer 复用常驻 dev。改端口要走 E2E_PORT 环境变量同步 Playwright 与 bun run dev(dev 的端口是 PORT,不是 E2E_PORT),两个变量都设置才会真的换端口;只设一边会卡在「启不来就超时 120 秒」或请求打到原端口。E2E 在 Windows 上也有专门的 fix。webServer.command 自启前清理 .nuxt/nuxt.lock 走 bun -e rmSync,因为 Playwright 在 Windows 用 cmd.exe 而 cmd 里没有 rm。

集成测试要求 .env 的数据库字段齐全、库与初始化状态干净。test/integration/db/setup.ts:19 注释写「开发库同名,需谨慎:不要 init-db 重置」,DB_HOST / DB_PORT / DB_USER / DB_PASSWORD / DB_NAME 任一缺失会强抛,不再静默 fallback 到 localhost 等默认值,配置错误立刻显形而不是以假数据通过测试。integration/real/_setup.ts 的处理是:在 ${DB_NAME}_test 旁路库里跑 scripts/init-db.sql 幂等建表(TEST_DB_NAME 可覆盖),因此 dev 库本身不会被覆盖;integration/db/ 用的就是当前 DB_NAME 直连。E2E 的 ensureArticle 与 cleanupArticle 又依赖「开发库存在至少一篇文章」,清空后 comment.spec.ts 等用例会找不到可评论对象而中断:跑测试前先确认主库与旁路库都已经按 bun run db:init 初始化。

评论相关用例必须真实等待反 bot 时间窗。CommentInput.vue 写死了 5 秒的「页面加载后多久才允许提交」反 bot 判定(Date.now() - pageLoadTime < 5000),E2E 里三个用例各等 5200ms 以留余量,绕过判定会被服务端拒收。同样不能伪造 localStorage 或 sessionStorage,服务端另按「同 IP 60 秒」的提交间隔校验(comments.post.ts 的 commentInterval,默认从后台设置读取,也是 60),意味着 comment-reply.spec.ts 在父评论提交后必须再等 61 秒才能发回复。这条用例的 test.setTimeout(120_000) 就是给这两个等待留的时间。修改了后台「发布间隔」或改了客户端反 bot 阈值的提交都应在重跑前同步到 E2E 等待时间。