首页
/ Immich 测试体系全解:从服务端单测到 Docker 化的端到端测试实战

Immich 测试体系全解:从服务端单测到 Docker 化的端到端测试实战

2026-09-06 15:31:46作者:温玫谨Lighthearted

本篇指南基于 Immich 仓库官方的开发者测试文档 docs/docs/developer/testing.md 展开,系统讲解 Immich 服务端单元测试、端到端(e2e)测试与 Web UI 测试的完整运行方式,并结合 mise.toml 任务定义、vitest/Playwright 配置和 docker-compose 测试环境,深入剖析每个测试命令背后实际执行的构建链路与环境编排,帮助你在本地正确搭建、运行并理解 Immich 的全层测试体系。

一、测试分层总览:Immich 的三层测试结构

Immich 是一个高并发的自托管照片与视频管理方案,其质量保障由三层测试构成:

层级 位置 工具 覆盖对象
单元测试 server vitest 核心服务、工具函数、纯逻辑
中型(集成)测试 server/test/medium vitest + 真实数据库 仓储层、数据库交互
端到端测试 e2e vitest / Playwright 完整生产环境下的 API、CLI、Web

三层测试通过 mise 任务统一调度,避免了手工拼装命令的混乱。

二、服务端单元测试

2.1 运行方式

根据官方文档,单元测试通过以下命令运行:

# 首次运行前只需执行一次,安装 server 依赖
mise //server:install

# 运行单元测试
mise //server:test

这两个命令对应 server/mise.toml 中定义的任务:

  • server:install 实际执行 pnpm install --filter immich --frozen-lockfile,即按锁文件安装 server 包(immich)的依赖;
  • server:test 实际执行 vitest --config test/vitest.config.mjs,并显式把 ./node_modules/.bin 加入 PATH。

2.2 单元测试到底跑了什么

server/test/vitest.config.mjs 可以看到单元测试的精确边界:

export default defineConfig({
  test: {
    name: 'server:unit',
    root: serverRoot,
    globals: true,
    include: ['src/**/*.spec.ts'],
    coverage: {
      provider: 'v8',
      include: ['src/cores/**', 'src/services/**', 'src/utils/**', 'src/sql-tools/**'],
      exclude: [
        'src/services/*.spec.ts',
        'src/services/api.service.ts',
        'src/services/microservices.service.ts',
        'src/services/index.ts',
      ],
    },
    server: { deps: { fallbackCJS: true } },
    env: { TZ: 'UTC' },
  },
  plugins: [swc.vite(), tsconfigPaths()],
});

由此可以确认几个关键事实:

  • 测试文件约定:单元测试即 src/**/*.spec.ts,与业务代码同目录存放(例如 server/src/validation.spec.ts);
  • 覆盖范围:覆盖率统计聚焦于 src/cores/src/services/src/utils/src/sql-tools/ 四个目录,即业务核心逻辑,并排除了纯 API 转发与微服务调用等难以单测的胶水层;
  • 时区固定env: { TZ: 'UTC' } 保证时间相关断言在任何开发机上结果一致;
  • 编译加速:使用 unplugin-swc 替代 esbuild 做转译、vite-tsconfig-paths 解析路径别名,是 NestJS + TypeScript 项目提速的常见组合。

2.3 中型测试与 CI 完整检查

server/mise.toml 中还定义了文档未直接提及但 CI 会执行的 test-mediumci-unit 任务:

[tasks."test-medium"]
env._.path = "./node_modules/.bin"
run = "vitest --config test/vitest.config.medium.mjs"

[tasks.ci-unit]
run = [
  { task = ":install" },
  { task = "//:plugins" },
  { task = ":format" },
  { task = ":lint" },
  { task = ":check" },
  { task = ":test --run" },
]

其中 server/test/vitest.config.medium.mjs 将测试范围限定为 test/medium/**/*.spec.ts,并通过 globalSetup: ['test/medium/globalSetup.ts'] 在测试套件启动前完成环境准备(数据库等)。从 ci-unit 任务的编排看,CI 中一次完整的单测门禁顺序是:安装依赖 → 构建插件 → Prettier 格式检查 → ESLint → tsc --noEmit 类型检查 → vitest 运行。本地开发时若想对齐 CI 行为,可以直接执行 mise //server:ci-unit,而不必逐条手敲命令。

三、端到端(e2e)测试

3.1 运行步骤

官方文档给出的 e2e 完整流程分三步:

# 第 1 步:启动测试生产环境(交互式,会保持前台运行)
mise e2e

# 第 2 步:一次性环境准备(只需执行一次)
mise //e2e:ci-setup    # 安装 e2e、SDK 和 CLI 依赖
mise //:open-api      # 生成/同步 OpenAPI 类型与 SDK

# 第 3 步:在环境运行起来后执行测试
mise //e2e:test

3.2 每个命令背后的真实动作

mise e2e 启动了什么:对应根 mise.toml 中的 [tasks.e2e] 任务:

[tasks.e2e]
depends = "//:plugins"
dir = "e2e"
interactive = true
env = { COMPOSE_BAKE = true }
run = "docker compose -f ./docker-compose.yml up --remove-orphans"
depends_post = "//:e2e-down"

它先构建 Extism 插件(//:plugins),再进入 e2e 目录用 e2e/docker-compose.yml 拉起一套测试专用的生产环境,退出时自动执行 e2e-down 清理容器。该环境由四个服务组成:

服务 镜像/构建来源 端口 说明
immich-server server/Dockerfile 2285 被测服务,IMMICH_ENV: testing,禁用机器学习,测试素材挂载到 /test-assets
database 自带 vchord/pgvector 扩展的 Postgres 5435 → 5432 fsync=off 提升测试吞吐,健康检查后才允许 server 启动
redis valkey:9 队列/缓存依赖
e2e-auth-server packages/e2e-auth-server/Dockerfile 2286 本地 OAuth2 认证服务器,用于验证 OAuth 登录流程

几个值得注意的环境细节(均来自 e2e/docker-compose.yml):

  • IMMICH_MACHINE_LEARNING_ENABLED: 'false':e2e 环境不启动机器学习微服务,使测试不依赖 ML 模型;
  • IMMICH_TELEMETRY_INCLUDE: all:测试环境开启全量遥测,便于断言遥测相关行为;
  • ./test-assets:/test-assets:挂载测试素材(图片、视频样本),供上传、缩略图、元数据提取等用例消费;
  • cache_from 指向远程构建缓存,加速 server 镜像构建。

mise //e2e:ci-setup 做了什么:对应 e2e/mise.toml

[tasks.ci-setup]
run = [
  { task = "//:sdk:install" },   # pnpm --filter @immich/sdk install --frozen-lockfile
  { task = "//:sdk:build" },     # 构建 TypeScript SDK
  { task = "//packages/cli:install" },
  { task = "//packages/cli:build" },
  { task = ":install" },         # pnpm install --filter immich-e2e... --frozen-lockfile
]

这解释了文档中「安装 e2e、SDK 和 CLI 依赖」的含义:e2e 包(e2e/package.json)通过 workspace:* 直接依赖 @immich/sdk@immich/cli,因此必须先构建这两个 workspace 包,e2e 才能以类型安全的方式调用完整 API 与命令行工具。

mise //:open-api 做了什么:对应根 mise.toml[tasks.open-api],依次执行插件构建、server 安装与构建、sync-open-api(导出 OpenAPI 规范)、open-api-typescript(用 oazapfts 从 open-api/immich-openapi-specs.json 生成 TypeScript SDK 客户端 packages/sdk/src/fetch-client.ts)、open-api-dart(为移动端生成 Dart SDK)。e2e 测试依赖 SDK 客户端与 server 的类型同步,所以文档要求先执行这一步。

mise //e2e:test 做了什么

[tasks.test]
depends = ["//e2e:build", "//e2e:ci-setup"]
env._.path = "./node_modules/.bin"
run = "vitest --run"

它自动确保镜像已构建、依赖已安装,然后运行 e2e/vitest.config.ts

3.3 e2e 环境的智能自举

e2e/vitest.config.ts 有一个精巧的设计:vitest 启动时会先探测 http://127.0.0.1:2285/api/server/ping——

const skipDockerSetup = process.env.VITEST_DISABLE_DOCKER_SETUP === 'true';
const globalSetup: string[] = [];
if (!skipDockerSetup) {
  try {
    await fetch('http://127.0.0.1:2285/api/server/ping');
  } catch {
    globalSetup.push('src/docker-compose.ts');
  }
}
  • 如果你已经按文档手动执行过 mise e2e(ping 成功),就跳过重复启动;
  • 如果环境没起,则自动注册 e2e/src/docker-compose.ts 作为 globalSetup,由它执行 docker compose up --build ...,并监听容器输出、直到出现 Immich Microservices is running 字样(60 秒超时)才放行测试;
  • 设置 VITEST_DISABLE_DOCKER_SETUP=true 可以强制跳过自举,只针对已运行的环境跑测试。

该配置还定义了执行策略:include: ['src/specs/server/**/*.e2e-spec.ts'](只跑 server 目录下的 API/CLI 类用例)、testTimeout: 15_000pool: 'threads'maxWorkers: 1(API 测试串行执行以避免相互干扰)、retry: process.env.CI ? 4 : 0(CI 中对瞬时失败重试 4 次,本地不重试)。

3.4 覆盖场景与用例清单

官方文档列出的 e2e 检查范围包括:

  • 认证与授权(Authentication and authorization)
  • 查询参数、请求体与 URL 的校验(Query param, body, and url validation)
  • 响应状态码(Response codes)
  • 缩略图生成(Thumbnail generation)
  • 元数据提取(Metadata extraction)
  • 库扫描(Library scanning)

e2e/src/specs 的实际用例清单可以印证这些声明的落地位置:

e2e/src/specs/
├── server/
│   ├── api/       # album、api-key、asset、download、integrity、jobs、library、
│   │              # map、oauth、person、search、server、session、shared-link、
│   │              # stack、system-config、system-metadata、tag、trash、user、user-admin
│   ├── cli/       # login、server-info、upload、version
│   └── immich-admin/
├── web/           # asset-viewer(detail-panel、navbar、slideshow)、album、auth、
│                  # duplicates、integrity、photo-viewer、shared-link、user-admin、websocket
└── maintenance/   # database-backups(server/web 各一份)、maintenance

其中 oauth.e2e-spec.ts 对应文档所说的认证与授权(配合 2286 端口的独立认证服务器),asset/library 相关用例覆盖上传、缩略图、元数据与库扫描,user/user-admin/session/api-key 等用例覆盖 URL 与参数校验及响应码断言。测试依赖 e2e/src/fixtures.tse2e/src/generators.ts(含 generators/ 下的随机数据生成器)与 e2e/src/ui(含 mock-network/ 网络拦截、specs/ UI 层用例)等工具模块构造请求与断言。

3.5 Web UI 测试(Playwright)

e2e/mise.toml 中还定义了文档未展开但同属 e2e 体系的 Playwright 任务:

[tasks."test-web"]
depends = ["//e2e:build", "//e2e:ci-setup", "//e2e:playwright-install"]
env._.path = "./node_modules/.bin"
run = "playwright test"

mise //e2e:test-web 会先安装浏览器(playwright install)再执行浏览器测试。从 e2e/playwright.config.ts 可以看到它划分为三个 project:

  • websrc/specs/web):走真实后端 http://127.0.0.1:2285 的 Web 页面流程,串行执行(workers: 1);
  • uisrc/ui/specs):基于 e2e/src/ui/mock-network 做网络 mock 的纯 UI 测试,可完全并行;
  • maintenancesrc/specs/maintenance/web):维护模式(maintenance mode)下的页面行为。

配置中 webServer 会在测试前自动执行 docker compose up --build ... 拉起 2285 端口的环境并复用已存在的实例(reuseExistingServer: true),失败时自动截图、重试时录制 trace(trace: 'on-first-retry'),CI 环境下固定 4 个 worker 并重试 4 次。此外还支持通过 .env 中的 PLAYWRIGHT_BASE_URLPLAYWRIGHT_SLOW_MOPLAYWRIGHT_DISABLE_WEBSERVER 等变量调整行为。

四、环境管理与清理

所有测试环境都是 Docker Compose 项目,因此与 up 对应的 down 任务同样重要(定义在根 mise.toml):

命令 作用
mise e2e 启动 e2e 生产测试环境(前台)
mise //:e2e-down 手动清理 e2e 环境
mise e2e-update --build -V 参数重建镜像后启动
mise //e2e:test 运行 API/CLI 层 vitest e2e
mise //e2e:test-web 运行 Playwright Web UI 测试

由于 mise e2e 带有 depends_post = "//:e2e-down",按 Ctrl+C 退出前台任务时会自动清理容器,不会留下残留的 immich-e2e-* 容器与网络。

五、总结

Immich 的测试体系以 docs/docs/developer/testing.md 描述的三条命令为入口,背后是一整套由 mise 任务图编排的链路:server:test 用 vitest 以 UTC 时区、SWC 加速运行 src/**/*.spec.ts 单测并统计核心模块覆盖率;mise e2ee2e/docker-compose.yml 拉起 server + Postgres + Redis + OAuth 认证服务器的完整测试环境;mise //e2e:ci-setupmise //:open-api 保证 SDK、CLI 与 OpenAPI 类型先于测试就绪;mise //e2e:test 通过智能自举(ping 探测或自动 docker compose up)执行 API/CLI 层 21+ 个端点域与 4 个 CLI 命令的端到端断言,Playwright 则补齐 Web 页面层的验证。理解 mise.tomlserver/mise.tomle2e/mise.toml 中的任务定义,就能把文档中的每条命令对应到真实的构建与测试链路,为二次开发时的回归验证提供可靠基线。

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