首页
/ RealWorld 规范中心实战指南:openapi.yml 契约、Hurl 测试套件与 Playwright 前端校验是如何支撑 100+ 框架实现的

RealWorld 规范中心实战指南:openapi.yml 契约、Hurl 测试套件与 Playwright 前端校验是如何支撑 100+ 框架实现的

2026-09-03 15:58:41作者:咎竹峻Karen

RealWorld 被称为 “The mother of all demo apps”:它用同一套 Medium.com 克隆应用(Conduit)规范,驱动了上百种语言与框架的前后端实现。本篇以仓库根目录 README.md 为主线,结合 specs/api/openapi.ymlspecs/api/hurlspecs/e2e 等真实文件,讲清楚这个“规范与文档中心”仓库的模块化设计、如何对后端实现跑 API 测试、对前端实现跑共享 E2E 测试,以及如何本地构建其文档站点,帮助你在选型、学习或新增一个 RealWorld 实现时有的放矢。

1. 定位:同一份 API 契约,任意前端搭配任意后端

README.md 的核心主张是:大多数 “todo 示例” 只能让你粗略了解一个框架的能力,却传达不出构建真实应用所需的知识,也暴露不出最小 demo 从不需要面对的真实世界约束;而 RealWorld 的做法是为每个框架提供同一个 demo 应用,落在“简单性与覆盖面之间的甜点区”。

其模块化根基在于:所有前端与后端实现都遵循同一份 API 规范(README 原文:“Any frontend can be combined with any backend, because they all adhere to the same API spec”)。这意味着:

  • 你可以用任何框架写后端,只要它能通过完整的 API 规范测试套件;
  • 你可以用任何框架写前端,只要它使用共享 CSS 主题并通过共享 E2E 套件;
  • 前后端实现彼此解耦,可以独立开发、独立验证、自由组合。

README 同时列出了几个关键事实:

  • 已有 100 多种实现,使用不同的语言、库和框架;
  • 两个“规范合规”(spec-compliant)后端实现被单独点名:Nitro + Prisma + Zod(TypeScript)Django Ninja(Python),它们通过了完整的 API 规范测试套件;
  • 仓库提供共享 CSS 主题 让前端实现拥有完全一致的 UI/UX,提供共享 E2E 测试套件 用于验证前端实现;
  • 维护者包括 c4ffein(维护规范、测试套件与演示站点)与 Manuel Vila(Layr 框架与 CodebaseShow 的创造者)。

2. 仓库目录结构:规范中心而非可运行应用

与许多教程仓库不同,这个仓库本身不是一个可运行的应用CLAUDE.md 开宗明义:“This is the RealWorld spec & docs hub — not an implementation. Nothing here is a runnable app.” 各目录职责如下:

路径 角色
specs/api/ API 契约本体:openapi.yml 加上后端必须通过的 HurlBruno 测试套件
specs/e2e/ 用于验证前端的共享 Playwright 套件,含选择器契约 SELECTORS.md 与基线配置 playwright.base.ts
docs/ 基于 Astro/Starlight 构建的文档站点源码(含 package.jsonastro.config.mjstsconfig.json
assets/ 共享前端素材:CSS 主题、Logo 生成器 generate_assets.py 与各平台图标
Makefile 仓库的入口命令集合,make help 查看全部目标

Makefile 将仓库的两大工作流收敛为两组目标:Bruno 集合的生成/校验(bruno-generatebruno-check)与文档站点的生命周期(documentation-setupdocumentation-devdocumentation-dev-hostdocumentation-builddocumentation-previewdocumentation-clean)。

3. API 规范本体:openapi.yml 定义了哪些端点

specs/api/openapi.yml 是一份 OpenAPI 3.1.0 文档,标题为 “RealWorld Conduit API”,版本 2.0.0,按六大业务域打标签:Articles、Comments、Favorites、Profile、Tags、User and Authentication。从文件中的 paths 定义可以看到完整的端点面:

  • 认证与当前用户POST /users/login(登录,401/422 语义)、POST /users(注册,201/409/422)、GET /userPUT /user(均需 Token 认证);
  • 文章GET /articles/feed(只返回你所关注用户的最新文章,要求认证,支持 limit/offset)、GET /articles(全局最新,可选按 tagauthorfavorited 过滤并支持分页)、POST /articles(创建)、以及按 slug 的单篇 GET/PUT/DELETE;
  • 个人主页GET /profiles/{username}(认证可选)、POST|DELETE /profiles/{username}/follow(关注/取关,要求认证);
  • 评论与收藏:围绕文章 slug 的评论增删查,以及文章收藏/取消收藏。

值得注意的是,该契约不仅描述“正常路径”,还通过响应定义(如 UnauthorizedNotFoundConflictErrorGenericError)与测试套件共同约束了边界行为。例如注册重名/重邮返回 409,未认证访问受保护资源返回 401——这些在 specs/api/hurl 的 errors 系列文件中都有逐条断言(见下节)。

specs/api/README.md 对该目录的定位是:它定义了契约,并附带了“后端必须通过”的测试套件;README 中还提到对外的公开后端 api.realworld.show 无需 API key 即可使用(提供演示账号,且真实账号之间互相不可见),以及配套的 Angular 前端演示站点 demo.realworld.show

4. 后端验证:Hurl 是事实来源,Bruno 是生成的镜像

4.1 运行 API 测试

对任意一个运行中的后端,只需把 HOST 指向它,即可在 specs/api/ 目录下执行(出自 specs/api/README.md):

HOST=http://localhost:3000/api ./run-api-tests-hurl.sh
HOST=http://localhost:3000/api ./run-api-tests-bruno.sh

run-api-tests-hurl.sh 源码可以看到执行细节:

hurl --test \
  --jobs 1 \
  --variable "host=$HOST" \
  --variable "uid=$UID_VAL" \
  "${FILES[@]}"
  • HOST 默认 http://localhost:8000,可用环境变量覆盖;
  • uid 默认取 $(date +%s)$$(时间戳+进程号),用于为每次运行生成互不冲突的用户名(如 auth_{{uid}}),避免测试账号串号;
  • --jobs 1 保证请求按顺序串行执行——因为这些 .hurl 文件是“多步场景脚本”,前面的 [Captures] 会被后面的 [Asserts] 依赖;
  • 不带参数时默认运行 hurl/*.hurl 的全部文件,也可以把特定 .hurl 文件作为参数传入,只跑某个领域(如 ./run-api-tests-hurl.sh hurl/pagination.hurl)。

Bruno 侧的 run-api-tests-bruno.sh 则按子文件夹逐个执行 bun x @usebruno/cli run <folder> --env local --env-var "host=$HOST" --sandbox safe,默认跳过 environments/ 目录。你当然也可以直接在 Bruno 应用中打开 specs/api/bruno 目录交互式地查看和运行请求。

4.2 测试套件的组织方式

hurl/ 目录按业务域 + 错误类别组织了 13 个场景文件:articles.hurlauth.hurlcomments.hurlfavorites.hurlfeed.hurlpagination.hurlprofiles.hurltags.hurl,以及 errors_auth.hurlerrors_articles.hurlerrors_authorization.hurlerrors_comments.hurlerrors_profiles.hurl。Bruno 集合则是按同结构镜像的目录(articles/auth/comments/ 直到 errors-* 系列),文件以编号命名保证执行顺序。

auth.hurl 为例,一个文件就是一条完整的场景链:注册 → 登录 → 获取当前用户 → 更新 bio → 验证持久化 → 空字符串 bio 归一化为 null → null 赋值被接受 → 更新 image → image 空串归一化 → 更新 username/email 并验证。每个请求都带 [Asserts](jsonpath 精确断言,如 jsonpath "$.user.bio" == null)与 [Captures](如 token: jsonpath "$.user.token" 供后续请求的 Authorization: Token {{token}} 头复用)。

这些断言揭示的正是“规范合规”的深意——它校验的不只是 CRUD 可用性,还包括一批容易在真实实现里踩坑的边界行为:

  • 可空字段的空字符串归一化PUT /userbio/image 设为 "" 必须返回 null,且归一化要持久化(errors_auth.hurl 的 06/07/14/15 号文件及 specs/api/bruno/auth 中的 06、14 等用例逐条验证);
  • 必填字段的空值拒绝username/email 更新为空字符串或 null、密码空串或不足 8 位必须被拒绝(specs/api/bruno/errors-auth 的 12–18 号用例);
  • 重复标题允许12-duplicate-titles-are-allowed-each-gets-a-unique-slug.bru 验证两个同名文章各自获得唯一 slug;
  • 标签更新语义:更新文章时不带 tagList 应保留原标签,空数组 [] 应真正清空,而 null 应被拒绝(specs/api/bruno/articles 的 12–15 号用例);
  • 跨用户授权:user B 删除/更新 user A 的文章或评论必须得到 403,且失败的删除不得产生副作用(specs/api/bruno/errors-authorization);
  • 分页语义limit/offset 组合下最新文章优先,offset 1 取到第二页(specs/api/bruno/pagination)。

4.3 Hurl 为源、Bruno 生成:单一事实来源的工程纪律

specs/api/README.md 明确声明:“The Hurl files are the source of truth. The Bruno collection is generated with make bruno-generate and kept in sync via CI (make bruno-check).” 对应 Makefile 中的实现:

bruno-generate:
	bun specs/api/hurl-to-bruno.js

bruno-check:
	bun specs/api/hurl-to-bruno.js --check

即:改动测试用例时只改 Hurl 文件,然后用 hurl-to-bruno.js 重新生成 specs/api/bruno;CI 通过 --check 模式失败于任何“手工改 Bruno”的漂移。CLAUDE.md 同样强调这条纪律:“Don't hand-edit specs/api/bruno/ — change the Hurl files and regenerate.” 这套“一个源、一个生成器、一个 CI 检查”的模式,保证了人类可读的交互式集合与可自动化的脚本测试永不失同步。

5. 前端验证:共享 Playwright E2E 套件与选择器契约

对前端实现,仓库提供的是 specs/e2e/:一组可直接被实现仓库“vendored”(作为 ./e2e 引入)的 Playwright 测试。场景文件覆盖 auth.spec.tsarticles.spec.tscomments.spec.tsnavigation.spec.tssettings.spec.tssocial.spec.tserror-handling.spec.tsnull-fields.spec.tsxss-security.spec.ts 等,helpers/ 目录则封装了 API 辅助(api.ts)与各领域操作(articles.tsauth.tscomments.tsprofile.tssetup.ts)。

集成方式在 playwright.base.ts 的注释中给出官方模板:实现在自己的根目录 playwright.config.ts 中扩展基线配置、覆盖 baseURL 并声明 webServer

import { defineConfig } from '@playwright/test';
import { baseConfig } from './e2e/playwright.base';

export default defineConfig({
  ...baseConfig,
  use: { ...baseConfig.use, baseURL: 'http://localhost:3000' },
  webServer: {
    command: 'npm run start',
    url: 'http://localhost:3000',
    reuseExistingServer: !process.env.CI,
    timeout: 120_000,
  },
});

基线配置本身的取舍值得注意:testDir: './e2e'fullyParallel: falseworkers: 1(严格串行,规避共享状态的竞态)、retries: CI ? 2 : 1forbidOnly: !!CI(CI 中禁止遗留 test.only)、trace: 'on-first-retry'screenshot: 'only-on-failure'(失败时保留取证材料),并只配置了 Desktop Chrome 项目。

前端还必须满足 SELECTORS.md 定义的选择器契约——从源码结构看,这是为了让任何框架的前端都能被同一套测试以统一的 testid/CSS 选择器定位,从而让“通过共享 E2E”成为前端实现合规的客观判据。

6. 文档站点:Astro/Starlight 源码与 Makefile 入口

docs/ 目录是一个完整的 Astro 站点工程(docs/package.jsondocs/astro.config.mjsdocs/bun.lock),内容组织在 docs/src/content/docs 下,与 README 的 “Learn more” 一一对应:specifications/backendendpoints.mdapi-response-format.mderror-handling.mdtests.md 等)、specifications/frontendapi.mdstyles.mdtests.mdtemplates.mdrouting.md)、implementation-creationintroduction.mdfeatures.mdexpectations.md)以及 community 文档。

本地工作流全部收敛在 Makefile 中(注意仓库约定使用 bun 而非 npm):

make documentation-setup      # cd docs && bun install
make documentation-dev       # 本地开发服务器
make documentation-dev-host  # 暴露到局域网
make documentation-build     # 生产构建
make documentation-preview   # 预览构建产物
make documentation-clean     # 清理 .astro / dist / node_modules

7. 使用指引:规范中心的三条红线

综合 README.mdCLAUDE.md,这个仓库的使用者画像有明确分工:

  1. 如果你是在为某个框架创建 RealWorld 实现:实现位于各自独立的仓库中,不在本仓库。后端对着 specs/api/ 开发并用第 4 节的脚本验收;前端使用 assets/theme/styles.css 主题并引入 specs/e2e/ 做验收;完整入门与期望见 docs/src/content/docs/implementation-creation/introduction.md
  2. 如果你的实现仓库以 submodule/依赖形式内嵌了本仓库(E2E 套件即被这样以 ./e2e 消费):CLAUDE.md 给出的规则是“修你的实现,永远不要为了通过而编辑规范或测试——specs/ 下的文件是失败实现必须服从的事实来源”。
  3. 仓库内部约定:一切用 bun;Hurl 是 API 套件的事实来源,Bruno 是生成镜像;文档 URL 带尾斜杠。贡献流程与提交信息规范(<type>(<scope>): <subject>,type 限 docs/feat/fix,scope 限 specs/project)见 CONTRIBUTING.md;框架 Logo 的授权与署名细节见 docs/non-included/LICENSES_LOGOS.md

8. 小结

RealWorld 主仓库的价值不在某一份框架代码,而在于它把“跨栈互操作”变成了可验证的工程事实:一份 openapi.yml 契约 + 一套 Hurl 事实来源/Bruno 镜像的后端测试 + 一套 Playwright/选择器契约的前端测试 + 一个共享 CSS 主题。理解了这套“契约先行、测试即验收标准”的结构,你无论是想快速吃透某个框架如何写真实业务应用(去对应的实现仓库学习),还是想为尚未覆盖的框架新增一个实现(对着本仓库的规范与测试自证合规),都有了清晰的路线图。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
981
502
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384