FForm Platform
enzh-CN

测试与质量

使用分层自动测试和部署门禁,让可配置平台能够安全演进。

FormPlatform 测试项目指南

English: TESTING_GUIDE.md

1. 当前测试体系

FormPlatform 使用分层验证,而不是让一种测试承担全部责任:

位置目的外部依赖
前端单元/契约`src/FormPlatform.Host/ClientApp/src/**/*.test.js`纯运行时逻辑、组件公共契约和回归规则Node.js、Vitest
静态检查各项目/脚本JSON/XML/JS/C# 结构、链接、格式和 manifest无或本机 SDK
PostgreSQL 集成测试`tests/FormPlatform.PostgreSql.Tests`真实 ORM、事务、Reference、migration/checksumDocker
SQL Server 集成测试`tests/FormPlatform.SqlServer.Tests`真实 SQL Server ORM、生成主键/分页、migration/checksumDocker
Playwright E2E`tests/e2e`浏览器、认证、路由、表单、模块和错误 UX已启动 Host、Chromium、测试账号
全模块构建`eng/build-all.ps1`Host/SDK/Sample/Todo/ResourceBooking 二进制兼容Node、.NET、同级模块仓库
人工验收浏览器/数据库/日志Designer、打印、支付/OIDC 等环境相关流程对应环境

当前 GitHub CI 已形成七道强制门禁:前端 Vitest、全模块构建并组装可部署 runtime、SDK/module 兼容性验证、PostgreSQL Testcontainers 集成测试、SQL Server Testcontainers 集成测试、使用独立 PostgreSQL 和一次性管理员账号启动真实 Host 的 Playwright 核心流程,以及最终的发布 runtime 完整性门禁。最终门禁依赖此前全部成功。

2. 测试前准备

基础版本:.NET 10 SDK、Node.js 22。PostgreSQL tests 需要 Docker Desktop/Engine 可运行 Linux container。E2E 需要目标 FormPlatform 已启动,且 ResourceBooking 用例需要模块已部署和客户端扩展已启用。

不要让自动测试连接开发或生产数据库。Testcontainers 会创建临时 PostgreSQL;E2E Host 也应使用专用测试数据库和秘密。

3. PostgreSQL 集成测试

项目:`tests/FormPlatform.PostgreSql.Tests/FormPlatform.PostgreSql.Tests.csproj`。

运行:

dotnet test tests/FormPlatform.PostgreSql.Tests/FormPlatform.PostgreSql.Tests.csproj -c Release

`PostgreSqlFixture` 启动 `postgres:17-alpine` container,实现 SDK 的 `IDatabaseConnectionFactory`。同一 xUnit collection 共享 container,测试通过独立表/数据或清理保证隔离。

现有覆盖:

  • `DynamicRepositoryTests`:真实 provider 上的动态 Entity、CRUD、query/reference/transaction 行为;
  • `ModuleMigrationTests`:migration 只记录一次、checksum 变化失败、Core/Commerce legacy schema 由 migration 建立。

3.1 增加 ORM 集成测试

测试应使用真正的 Entity Model 和 UnitOfWork,不 mock SQL dialect:

[Fact]
public async Task Query_projects_only_requested_fields()
{
    var factory = Factory();
    await using var work = await factory.BeginAsync("Management");
    // Arrange a unique test row through the repository.
    // Query with an explicit projection.
    // Assert values and absence of unrequested attributes.
    await work.CommitAsync();
}

每个测试使用唯一 ID/name;如果创建固定表,使用唯一表名或 class fixture 一次创建。不要依赖测试执行顺序。

3.2 SQL Server 集成测试

项目:`tests/FormPlatform.SqlServer.Tests/FormPlatform.SqlServer.Tests.csproj`。

dotnet test tests/FormPlatform.SqlServer.Tests/FormPlatform.SqlServer.Tests.csproj -c Release

`SqlServerFixture` 使用 Testcontainers 启动独立 SQL Server 2022。测试真实执行 SQL Server dialect 的 CRUD、filter、offset 分页与数据库生成主键路径,并完整应用 Core/Commerce migration 链两次,验证 checksum 保护;不连接本机安装的 SQL Server。

3.3 增加 migration 测试

至少验证:空库可执行;第二次执行不重复;历史表记录 module/id/checksum;修改同一 ID 内容会失败;新 migration 可接在旧 migration 后;三 provider SQL 在各自 CI 中最终都应实际执行。当前自动集成只覆盖 PostgreSQL,因此 MySQL/SQL Server 仍需要后续 container/job 或发布前环境测试。

3.4 Testcontainers 故障排查

  • 无法连接 Docker:先运行 `docker info`。
  • 拉取镜像失败:检查代理/registry;测试本身不应硬编码本机数据库。
  • container 启动慢:首次镜像下载正常,CI 可使用 layer cache。
  • 随机主键/表冲突:测试共享 container,使用唯一数据并清理。
  • Windows volume/端口问题:当前 fixture 使用随机端口,不要自行固定 5432。

4. 发布 runtime 完整性门禁

所有前置 CI 门禁成功后,`eng/validate-release-runtime.ps1` 会检查 staged runtime:

  • Host、SDK、契约程序集、appsettings、SPA 引用的静态资源、模块 manifest 与入口程序集必须齐全;
  • `appsettings.json` 不得包含非空连接字符串、密码、secret、加密密钥、API key 或私钥;部署秘密只能放环境变量、User Secrets 或外部 deployment secrets 文件;
  • `DataAccess:IncludeSqlParameterValues` 必须为 `false`;
  • 再次调用模块二进制兼容验证,并生成列出全部交付文件 SHA-256 的 `release-manifest.json`。

在人工发布前,可在 `eng/prepare-ci-runtime.ps1` 后执行相同检查:

./eng/validate-release-runtime.ps1 -RuntimeDirectory .ci/runtime -WriteManifest

CI 的 `formplatform-release-candidate` artifact 是已测试、带完整性清单的候选发布物;分支保护应要求 `release-readiness` 成功。

5. Playwright E2E

目录:`tests/e2e`,配置默认 base URL `http://localhost:5080`,Chromium 串行执行,失败保留 trace/screenshot。

首次安装:

cd tests/e2e
npm install --no-audit --no-fund
npm run install:browsers

准备测试 Host:

1. 使用独立测试数据库和测试 secrets。

2. 应用 migrations。

3. 创建测试系统用户并分配所需角色。

4. 部署需要测试的 Todo/ResourceBooking 模块。

5. 构建客户端并启动 Host。

设置环境变量并运行:

$env:FORMPLATFORM_E2E_BASE_URL='http://localhost:5080'
$env:FORMPLATFORM_E2E_USER='e2e-admin'
$env:FORMPLATFORM_E2E_PASSWORD='use-a-secret-value'
cd tests/e2e
npm test

交互调试:

npm run test:ui
npx playwright test specs/core.spec.js --headed --debug
npx playwright show-trace test-results/<result>/trace.zip

5.1 现有核心流程

  • `/form-designer` 可匿名直接打开;
  • runtime 返回 Todo 与 ResourceBooking 客户端扩展;
  • 登录后 Form Center 加载;
  • `/resource-booking` 直接访问和刷新不被 SPA fallback 重定向;
  • 服务器提交 409 后,表单仍挂载并显示统一错误。

登录用例在本地未设置账号环境变量时会 `skip`;CI 缺少账号时则直接失败,避免“绿色”结果掩盖未执行的认证流程。

5.2 编写稳定 E2E

优先使用 role/label/test id,不依赖 Tailwind class 或 DOM 层级:

test('created record navigates to its id', async ({ page }) => {
  await page.goto('/forms/<form-id>')
  await page.getByLabel('Name').fill('E2E item')
  await page.getByRole('button', { name: /submit/i }).click()
  await expect(page).toHaveURL(/\/forms\/[^/]+\/[0-9a-f-]{36}$/)
  await expect(page.getByText(/saved/i)).toBeVisible()
})

只 mock 不属于当前测试目标的边界。例如测试错误 UX 时 route fulfill 409 是合理的;测试真实 ORM 保存时不要 mock submissions。每个测试自行建立/清理数据,或调用测试专用 API/fixture,不依赖前一个测试。

5.3 必须覆盖的关键矩阵

  • Designer / Preview / Viewer 三模式;
  • 匿名、系统用户、respondent、管理员代填;
  • 新建、更新、删除、失败保留状态;
  • Pagination 草稿/完成、必填 Checkbox/Radio、隐藏字段;
  • DataGrid inline/modal/side pane、排序/搜索/导出;
  • AsyncSelect/Tree filter 和 Reference;
  • 模块直接路由、刷新、client extension 加载;
  • 中英文、toast、字段错误位置;
  • 打印不包含 Admin Panel;
  • Survey 文件上传和授权下载。

6. 全模块构建验证

本地目录默认要求:

workspace/FormPlatform
workspace/Todo
workspace/ResourceBooking

运行:

cd FormPlatform
./eng/build-all.ps1 -Configuration Release

它依次执行 ClientApp npm ci/build、Host/SDK、Sample、Todo、ResourceBooking client/server。缺少模块目录或 manifest 会失败,不会静默跳过。

快速只验证服务器兼容时可使用 `-SkipClientBuild`;也可通过 `-TodoDirectory`、`-ResourceBookingDirectory` 指定路径。这个脚本验证编译兼容,不替代数据库或浏览器测试。

GitHub workflow 需要 repository variables:

  • `FORMPLATFORM_TODO_REPOSITORY`
  • `FORMPLATFORM_RESOURCE_BOOKING_REPOSITORY`

私有模块仓库另设只读 `FORMPLATFORM_MODULES_TOKEN`。任何模块未构建都应让 CI 失败。

构建 job 使用 `eng/prepare-ci-runtime.ps1` 将 Host、Vue 静态文件和三个模块整理到 `.ci/runtime`。兼容性 job 随后执行:

./eng/validate-module-compatibility.ps1 -RuntimeDirectory .ci/runtime

该命令通过 Host 的 `--validate-modules` 模式加载真实部署包,验证 manifest schema/name、模块版本与程序集版本、SDK/Platform 版本区间、SDK 引用版本,并拒绝仍引用私有 Host 程序集的旧模块。它不连接数据库,也不启动 HTTP 服务。

Playwright job 使用 `postgres:17-alpine` service,禁用与核心用例无关的大型地理数据 seed,执行 migration、系统表单和模块初始化后再运行浏览器测试。Host log、HTML report、trace、screenshot 和 PostgreSQL TRX 都以短期 artifact 保存。仓库分支保护应要求四个 job 全部成功。

7. 客户端单元、契约和静态测试

ClientApp 使用 Vitest 测试 substitution、Action Context、validation/cache/debounce、Pagination、Checkbox/Radio、DataGrid model 和内置组件契约:

cd src/FormPlatform.Host/ClientApp
npm run test:unit
npm run test:unit:watch

契约详见客户端组件契约。Vitest 测纯函数和运行时规则;控件真实交互仍优先 Playwright E2E,而不是只对实现细节做 shallow mock。生产构建继续负责 Vue template、动态 import 和 bundling。

手动静态检查还包括:`node --check` 对普通 JS、JSON/XML 解析、`git diff --check`、Markdown 相对链接检查、manifest schema/version 检查。Vue SFC 不能只靠 `node --check`,必须经过 Vite 编译或 Vue parser。

8. API 与错误契约测试

所有失败至少断言 status、content type 和稳定字段:

{
  "code": "inventory.conflict",
  "messageKey": "inventory.conflict",
  "fallback": "...",
  "fieldErrors": {},
  "status": 409,
  "traceId": "..."
}

500 不应包含连接字符串、SQL、密码或堆栈。401/403/404 空结果也应由 status-code middleware 补成同一协议。客户端测试应确认字段错误进入当前 editor/FormReader,而不是主页面或别的嵌套表单。

9. 手工验收

自动测试后仍需针对环境相关功能检查:OIDC callback、SMTP、PayPal/Stripe/WeChat sandbox、打印/PDF、真实浏览器 date/time、IIS/Nginx headers、大文件、生产数据库权限和备份恢复。

最小发布 smoke:健康启动无 migration/module 错误;登录/退出;Form Center;打开 Designer;新建并更新一条普通记录;提交一份 Survey;DataGrid 查询;一个模块路由;日志中无未处理异常。

10. 测试数据和安全

生产或共享环境测试账号/密码进入 CI secret,不写进 spec。当前 E2E workflow 的固定密码只属于每次 job 新建并销毁的隔离数据库,不得在任何真实环境复用。测试文件不含真实个人数据。支付/OIDC 使用 sandbox。SQL 参数日志在测试敏感流程也保持关闭,除非使用完全虚构数据。失败 artifact/trace 可能包含表单内容,设置保留期和访问权限。

11. 新功能的测试策略模板

开发前填写:

问题答案示例
纯领域规则?xUnit unit test
依赖 ORM/provider?PostgreSQL integration
改 schema?migration first/second/checksum test
改 API/错误?integration + contract assertions
改 Vue 交互/路由?Playwright
改三 provider SQL?provider-specific CI/manual environment
涉及身份/权限?allow/deny matrix
涉及打印/外部服务?manual/sandbox smoke

修复 bug 时先建立能重现问题的测试,再修代码;验证解决后,重新评估临时 workaround 是否仍需保留。

12. 推荐 CI 阶段

1. Frontend Vitest 和组件契约;

2. ClientApp production build,以及 Host + SDK + all modules build;

3. 组装部署 runtime 并通过 Host 验证所有 SDK/module;

4. PostgreSQL integration;

5. 启动隔离 PostgreSQL/Host,运行 Playwright core;

6. 保存 runtime、TRX、Host log 和失败 traces;

7. SQL Server migration/ORM Testcontainers 集成测试;

8. 发布 runtime 完整性门禁和 SHA-256 清单;

9. staging 外部服务 smoke,批准后生产。

CI 失败时先保留最早的根因日志,不被后续级联错误淹没。构建成功不等于 migration、模块加载或浏览器流程成功。