- 后端
- 微服务
【免费下载链接】loopback-next
LoopBack makes it easy to build modern API applications that require complex integrations.
LoopBack 4 的acceptance/目录存放了一组独立的工作区包,用于把@loopback/repository与各数据库连接器(loopback-connector-mysql、loopback-connector-postgresql、loopback-connector-mongodb、loopback-connector-cloudant)以及@loopback/logging扩展与 fluentd 的组合放到真实服务上进行验收级验证。这些测试不会被仓库根目录的npm test自动触发,必须手动运行,且每个包的运行方式略有差异(环境变量、Docker setup 脚本等)。读完本文,你将了解每个验收测试包的定位、完整的运行命令与环境变量参数、底层测试套件的编写模式,以及 setup 脚本的工作机制,从而能在本地或 CI 中独立跑通这套验收测试。
1. 验收测试的定位:独立于npm test的手动测试层
根目录 acceptance/README.md 对这一目录的定义非常明确:
This directory contains packages with acceptance-level tests. These tests are not invoked as part of root
npm test, you have to run them manually. Consult README files of individual packages for instructions on how to run the tests.
也就是说,验收测试包与普通的单元测试/集成测试有三点本质区别:
- 依赖外部真实服务:需要真实运行的 MySQL、PostgreSQL、MongoDB、CouchDB(替代 Cloudant)或 fluentd 实例,因此无法在纯本地 mock 环境下随根
npm test一起跑; - 由各个子包 README 分别给出运行说明,每个包都是独立的 npm 工作区成员;
- 运行方式统一:在每个包内执行
npm test,由pretest脚本先构建 TypeScript,再用lb-mocha执行编译产物dist/__tests__/**/*.js。
这一点可以从 package.json 中得到印证:根目录的mocha脚本 glob 是{packages,extensions,examples}/*/dist/__tests__/**/*.js,并不包含acceptance/*目录;但 workspaces 列表中又明确列出了acceptance/*,说明这些包参与 monorepo 的安装与构建,只是不参与默认测试执行。
当前仓库要求 Node.js 版本为22 || 24 || 26、npm>=7(见根 package.json 的engines字段),各个验收包(如 acceptance/repository-mysql/package.json)声明了相同约束。
acceptance/目录下共有 5 个验收包,各自对应一个@loopback/test-*私有包名:
| 目录 | npm 包名 | 验证对象 | 被测连接器/组件 |
|---|---|---|---|
| acceptance/repository-mysql | @loopback/test-repository-mysql | @loopback/repository+ MySQL | loopback-connector-mysql |
| acceptance/repository-postgresql | @loopback/test-repository-postgresql | @loopback/repository+ PostgreSQL | loopback-connector-postgresql |
| acceptance/repository-mongodb | @loopback/test-repository-mongodb | @loopback/repository+ MongoDB | loopback-connector-mongodb |
| acceptance/repository-cloudant | @loopback/test-repository-cloudant | @loopback/repository+ Cloudant | loopback-connector-cloudant |
| acceptance/extension-logging-fluentd | @loopback/test-extension-logging-fluentd | @loopback/logging+ fluentd | fluent/fluentd 容器 |
2. 统一模式一:npm test背后的脚本链
以 acceptance/repository-mysql/package.json 为例,各包(其余四个包的 scripts 基本一致)定义了如下脚本链:
"scripts": { "build": "lb-tsc", "clean": "lb-clean loopback-test-repository-mysql*.tgz dist *.tsbuildinfo package", "pretest": "npm run build", "test": "npm run mocha", "mocha": "lb-mocha \"dist/__tests__/**/*.js\"", "verify": "npm pack && tar xf loopback-test-repository-mysql*.tgz && tree package && npm run clean" }pretest保证每次npm test前先用lb-tsc(@loopback/build提供的 TypeScript 封装)重新编译;lb-mocha只执行编译后的dist/__tests__下的 JS 测试文件;verify用于检查npm pack产物的文件布局(files字段声明只打包README.md、dist、src,并排除__tests__目录)。
由于测试包都声明为"private": true,它们只服务于 monorepo 内部的测试目的,不会发布到 npm。
3. 统一模式二:数据源配置全部由环境变量驱动
每个数据库验收包都包含一个*.datasource.ts文件,把DataSourceOptions的配置项与环境变量一一对应,并为每个变量提供默认值。这正是各包 README 中HOST/PORT/USER/PASSWORD/DATABASE命令行的实现依据。
3.1 MySQL
acceptance/repository-mysql/src/tests/mysql.datasource.ts:
import {CrudFeatures, DataSourceOptions} from '@loopback/repository-tests'; const connector = require('loopback-connector-mysql'); export const MYSQL_CONFIG: DataSourceOptions = { connector, host: process.env.MYSQL_HOST ?? 'localhost', port: process.env.MYSQL_PORT ?? 3306, database: process.env.MYSQL_DATABASE ?? 'repository_tests', username: process.env.MYSQL_USER ?? 'root', password: process.env.MYSQL_PASSWORD ?? '', createDatabase: true, }; export const MYSQL_FEATURES: Partial<CrudFeatures> = { idType: 'number', freeFormProperties: false, emptyValue: null, supportsTransactions: true, };环境变量与默认值对照如下:
| 环境变量 | 含义 | 默认值 |
|---|---|---|
MYSQL_HOST | 主机 | localhost |
MYSQL_PORT | 端口 | 3306 |
MYSQL_USER | 用户名 | root |
MYSQL_PASSWORD | 密码 | 空字符串 |
MYSQL_DATABASE | 数据库名 | repository_tests |
createDatabase: true表示允许连接器在连接时自动创建数据库;而 test/schema.sql 本身是空的(仅有注释说明:repository 测试套件不要求任何种子数据),建库工作交给连接器完成。
MYSQL_FEATURES是传给共享测试套件的能力声明:MySQL 使用数字自增id、不支持任意自由属性(表结构固定)、支持事务。
3.2 PostgreSQL
acceptance/repository-postgresql/src/tests/postgresql.datasource.ts:
export const POSTGRESQL_CONFIG: DataSourceOptions = { connector, host: process.env.POSTGRESQL_HOST ?? 'localhost', port: process.env.POSTGRESQL_PORT ?? 5432, database: process.env.POSTGRESQL_DATABASE ?? 'repository_tests', username: process.env.POSTGRESQL_USER ?? 'root', password: process.env.POSTGRESQL_PASSWORD ?? 'pass', }; export const POSTGRESQL_FEATURES: Partial<CrudFeatures> = { idType: 'number', freeFormProperties: false, emptyValue: null, supportsTransactions: true, };| 环境变量 | 默认值 |
|---|---|
POSTGRESQL_HOST | localhost |
POSTGRESQL_PORT | 5432 |
POSTGRESQL_USER | root |
POSTGRESQL_PASSWORD | pass |
POSTGRESQL_DATABASE | repository_tests |
注意 PostgreSQL 包还带了一个真正的 test/schema.sql,会先DROP DATABASE IF EXISTS再创建repository_tests与repository_tests_new两个库(后者用于迁移类场景的对照)。
3.3 MongoDB
acceptance/repository-mongodb/src/tests/mongodb.datasource.ts:
export const MONGODB_CONFIG: DataSourceOptions = { connector, host: process.env.MONGODB_HOST ?? 'localhost', port: process.env.MONGODB_PORT ?? 27017, database: process.env.MONGODB_DATABASE ?? 'repository_tests', }; export const MONGODB_FEATURES: Partial<CrudFeatures> = { idType: 'string', supportsTransactions: false, };MongoDB 只需要MONGODB_HOST(localhost)、MONGODB_PORT(27017)、MONGODB_DATABASE(repository_tests)三个变量,没有用户名/密码项;能力声明上idType为string(字符串_id),且标记supportsTransactions: false——共享测试套件据此会跳过事务相关断言。
3.4 Cloudant(用 CouchDB 替代)
acceptance/repository-cloudant/src/tests/cloudant.datasource.ts 在常规 host/port 之外还会拼接url:
export const CLOUDANT_CONFIG: DataSourceOptions = { connector, host: process.env.CLOUDANT_HOST ?? 'localhost', port: process.env.CLOUDANT_PORT ?? 5984, username: process.env.CLOUDANT_USERNAME ?? 'admin', password: process.env.CLOUDANT_PASSWORD ?? 'pass', database: process.env.CLOUDANT_DATABASE ?? 'testdb', }; CLOUDANT_CONFIG.url = 'http://' + CLOUDANT_CONFIG.username + ':' + CLOUDANT_CONFIG.password + '@' + CLOUDANT_CONFIG.host + ':' + CLOUDANT_CONFIG.port; export const CLOUDANT_FEATURES: Partial<CrudFeatures> = { idType: 'string', supportsTransactions: false, hasRevisionToken: true, };hasRevisionToken: true表明 Cloudant 数据源使用修订令牌(revision token)做乐观并发控制,测试套件会据此执行_rev相关的冲突/更新校验。该包的环境变量为CLOUDANT_URL、CLOUDANT_HOST(默认localhost)、CLOUDANT_PORT(默认5984)、CLOUDANT_USERNAME(默认admin)、CLOUDANT_PASSWORD(默认pass)、CLOUDANT_DATABASE(默认testdb)。
由于 Cloudant 没有官方维护的 Docker 镜像,这个包用ibmcom/couchdb3镜像在本地模拟(两者绝大多数功能一致,差别在于 CouchDB 不支持 geosearch),见 acceptance/repository-cloudant/README.md。
4. 统一模式三:共享crudRepositoryTestSuite驱动验收用例
各数据库包的默认仓库验收测试都只有一小段“胶水代码”,真正庞大的 CRUD 用例来自@loopback/repository-tests(monorepo 内的 packages/repository-tests 包)。以 acceptance/repository-mysql/src/tests/mysql-default-repository.acceptance.ts 为例:
import {DefaultTransactionalRepository} from '@loopback/repository'; import { CrudRepositoryCtor, crudRepositoryTestSuite, } from '@loopback/repository-tests'; import {MYSQL_CONFIG, MYSQL_FEATURES} from './mysql.datasource'; describe('MySQL + DefaultTransactionalRepository', () => { crudRepositoryTestSuite( MYSQL_CONFIG, // Workaround for https://github.com/microsoft/TypeScript/issues/31840 DefaultTransactionalRepository as CrudRepositoryCtor, MYSQL_FEATURES, ); });三段式结构在所有数据库中完全一致:DataSourceOptions(第 3 节的配置)、仓库构造函数(这里统一用DefaultTransactionalRepository,即支持事务的默认仓库)、Partial<CrudFeatures>(能力开关)。crudRepositoryTestSuite会根据CrudFeatures自动裁剪不适用的用例(例如 MongoDB/Cloudant 标记不支持事务,事务用例就不会运行)。这意味着新增一个数据库的验收测试时,只要写好*.datasource.ts和一段describe调用,就能继承全部标准 CRUD 行为验证。
除了默认仓库套件,MySQL 与 PostgreSQL、MongoDB 各包还额外覆盖repository.execute()的原始查询能力。例如 acceptance/repository-mysql/src/tests/mysql-repository-execute.acceptance.ts:
describe('MySQL repository.execute()', () => { it('executes a parameterized native SQL query', async () => { const db = new juggler.DataSource(MYSQL_CONFIG); @model() class Product extends Entity { @property({id: true, generated: true}) id: number; @property({required: true}) name: string; } const repo = new DefaultTransactionalRepository<Product, number, {}>( Product, db, ); await db.automigrate(Product.modelName); const pen = await repo.create({name: 'Pen'}); await repo.create({name: 'Car'}); const result = await repo.execute('SELECT * FROM Product WHERE name = ?', [ 'Pen', ]); expect(toJSON(result)).to.deepEqual([toJSON(pen)]); }); });可以看到验收测试的完整生命周期:用环境变量构造juggler.DataSource→automigrate建表 → 写入数据 → 用参数化 SQL 执行查询并断言结果,全部依赖真实数据库服务。
5. 运行方式一:复用已有的数据库实例
5.1 MySQL
若本机已有监听localhost:3306的 MySQL,用户为root、空密码,直接进入包目录后执行:
npm test要指向其他实例(对应 acceptance/repository-mysql/README.md):
Linux & MacOS
MYSQL_HOST=<HOST> MYSQL_PORT=<PORT> MYSQL_USER=<USER> MYSQL_PASSWORD=<PASSWORD> MYSQL_DATABASE=<DATABASE> npm testWindows
SET MYSQL_HOST=<HOST> SET MYSQL_PORT=<PORT> SET MYSQL_USER=<USER> SET MYSQL_PASSWORD=<PASSWORD> SET MYSQL_DATABASE=<DATABASE> npm test5.2 PostgreSQL
acceptance/repository-postgresql/README.md 给出同样的双模式:本机root用户空密码的localhost:5432实例直接npm test,否则通过POSTGRESQL_HOST、POSTGRESQL_PORT、POSTGRESQL_USER、POSTGRESQL_PASSWORD、POSTGRESQL_DATABASE五个环境变量指定远端实例,命令行写法与 MySQL 完全对应。
5.3 MongoDB
acceptance/repository-mongodb/README.md:本机默认实例直接npm test;自定义实例则使用MONGODB_HOST、MONGODB_PORT、MONGODB_DATABASE三个变量(默认值分别为localhost、27017、testdb,其中前两个默认值与源码中mongodb.datasource.ts的repository_tests库名一致,README 中的testdb指的是 Docker 方式下的默认库名)。
5.4 Cloudant
acceptance/repository-cloudant/README.md 没有“本机零配置”路径,Cloudant 必须显式提供连接参数:
Linux & MacOS
CLOUDANT_URL=<URL> CLOUDANT_HOST=<HOST> CLOUDANT_PORT=<PORT> CLOUDANT_USERNAME=<USER> CLOUDANT_PASSWORD=<PASSWORD> CLOUDANT_DATABASE=<DATABASE> npm testWindows
SET CLOUDANT_URL=<URL> SET CLOUDANT_HOST=<HOST> SET CLOUDANT_PORT=<PORT> SET CLOUDANT_USERNAME=<USER> SET CLOUDANT_PASSWORD=<PASSWORD> SET CLOUDANT_DATABASE=<DATABASE> npm test6. 运行方式二:Docker 一键拉起服务(setup.sh)
没有本地数据库实例时,各包提供了setup.sh,前提是已安装 Docker(Linux、macOS、WSL 环境)。
6.1 MySQL / PostgreSQL / MongoDB:复用连接器自带的 setup 脚本
这三个包的setup.sh只有一行,直接转发到对应连接器包的 setup 脚本,例如 acceptance/repository-mysql/setup.sh:
source ./node_modules/loopback-connector-mysql/setup.shacceptance/repository-postgresql/setup.sh 在此基础上固定了数据库名:
source ./node_modules/loopback-connector-postgresql/setup.sh export POSTGRESQL_DATABASE=repository_tests调用方式与默认参数(摘自各包 README):
# MySQL:默认 localhost / 3306 / root / pass / testdb source setup.sh <HOST> <PORT> <USER> <PASSWORD> <DATABASE> # PostgreSQL:默认 localhost / 5432 / root / pass / repository_tests 与 repository_tests_new source setup.sh <HOST> <PORT> <USER> <PASSWORD> <DATABASE> # MongoDB:默认 localhost / 27017 / testdb source setup.sh <HOST> <PORT> <DATABASE>参数均为可选,脚本会拉起相应数据库容器并导出对应环境变量,随后再执行npm test即可。
6.2 Cloudant:自研的双文件 setup(shell + node)
Cloudant 包没有现成镜像可用,因此 acceptance/repository-cloudant/setup.sh 是自包含脚本,流程为:
- 检查
docker -v,未安装则终止; docker rm -f couchdb3清理旧容器;- 拉取并启动
ibmcom/couchdb3:latest镜像,映射$PORT:5984,通过COUCHDB_USER/COUCHDB_PASSWORD注入管理员账号(默认admin/pass); export CLOUDANT_HOST/PORT/USERNAME/PASSWORD/DATABASE五个环境变量;- 执行 docker.setup.js。
第 5 步单独拆成 Node 脚本的原因在 docker.setup.js 头部的注释中写明:“The setup is separated to two files is because this step is hard to achieve by curl”。脚本逻辑是向http://<host>:<port>/<database>发PUT请求创建数据库,并对“容器尚未就绪导致 socket 过早断开”的情况做重试,最后打印实例 URL 和npm test提示。默认参数为localhost、5984、admin、pass、testdb:
. setup.sh <HOST> <PORT> <USER> <PASSWORD> <DATABASE><USER>与<PASSWORD>会成为 couchdb3 容器的管理员账号,测试源码 cloudant.datasource.ts 中默认的admin/pass与之对应。
6.3 fluentd:testcontainers 全自动管理
logging-fluentd 包不需要手工 setup:它通过testcontainers在 mocha 生命周期内自动启动/停止 fluentd 容器,只需执行:
npm test其devDependencies中同时列有@loopback/logging与testcontainers(见 acceptance/extension-logging-fluentd/package.json)。
7. 深入 fluentd 验收测试:从 fluentd.conf 到断言
acceptance/extension-logging-fluentd 验证的是@loopback/logging组件向 fluentd 发送日志的链路。
7.1 fluentd 侧配置
fluentd 使用仓库内的 acceptance/extension-logging-fluentd/fixtures/etc/fluentd.conf:
<source> @type http port 9880 bind 0.0.0.0 </source> <source> @type forward port 24224 </source> <match **> @type file path /fluentd/log/ <buffer> timekey 1d timekey_use_utc true timekey_wait 10m </buffer> </match>即暴露 HTTP 输入端口9880与 forward 端口24224,所有日志落到/fluentd/log/下的文件,缓冲等待 10 分钟刷盘(测试通过docker exec直接cat /fluentd/log/*.log读取,不依赖缓冲刷写时机)。
7.2 容器生命周期管理
acceptance/extension-logging-fluentd/src/tests/fixtures/fluentd.docker.ts 中startFluentd()用GenericContainer('fluent/fluentd')启动容器:
- 容器名
fluentd_lb4,暴露24224与9880; - 环境变量
FLUENTD_CONF=fluentd.conf指定配置文件; - 把仓库的
fixtures/etc目录以只读方式挂载到容器/fluentd/etc; - 启动后把
FLUENTD_SERVICE_HOST、FLUENTD_SERVICE_PORT_TCP、FLUENTD_SERVICE_PORT_HTTP写回process.env,测试用例据此决定连接目标。
mocha 根级before/after钩子负责在套件前后启动/停止容器,且带两条保护逻辑:已存在FLUENTD_SERVICE_HOST时不重复启动(允许复用外部 fluentd);CI 环境下只有process.platform === 'linux'才启动容器。
7.3 测试用例:三种行为断言
acceptance/extension-logging-fluentd/src/tests/acceptance/fluent.acceptance.ts 验证了LoggingComponent的三种行为:
async function givenAppWithCustomConfig() { app = givenApplication(); app.configure(LoggingBindings.FLUENT_SENDER).to({ host: process.env.FLUENTD_SERVICE_HOST ?? '127.0.0.1', port: +(process.env.FLUENTD_SERVICE_PORT_TCP ?? 24224), timeout: 3.0, reconnectInterval: 600000, // 10 minutes }); app.component(LoggingComponent); }binds a fluent sender:从上下文取LoggingBindings.FLUENT_SENDER,sender.emit({greeting: 'Hello, LoopBack!'})后等待 500ms,断言 fluentd 落盘日志匹配/LoopBack\s+\{"greeting"\:"Hello, LoopBack!"\}/;binds a winston transport for fluent:取LoggingBindings.WINSTON_LOGGER,logger.log('info', 'Hello, LoopBack!')后断言日志形如LoopBack {"level":"info","message":"Hello, LoopBack!"},证明 winston 传输层与 fluent 已打通;throws error if fluent is not configured:先app.unbind(BindingKey.buildKeyForConfig(LoggingBindings.FLUENT_SENDER))移除配置,再断言app.get(LoggingBindings.FLUENT_SENDER)被拒绝且错误信息为Fluent is not configured. Please configure logging.fluent.sender.——验证了缺失配置时的快速失败路径。
每个用例开头都有if (process.env.FLUENTD_SERVICE_PORT_TCP == null) return this.skip();的守卫:容器未能启动时用例自动跳过而不是失败,保证在非 Linux CI 等受限环境下套件仍然“绿”。
LoggingBindings.FLUENT_SENDER配置项中timeout: 3.0、reconnectInterval: 600000等参数展示了 fluent sender 的可用配置形态:host、port、timeout(秒)、reconnectInterval(毫秒)。
8. 各包文件结构速查
每个验收包都遵循相同的目录约定,便于按图索骥:
acceptance/ ├── README.md # 目录总说明 ├── repository-mysql/ │ ├── README.md # 运行说明 │ ├── package.json # @loopback/test-repository-mysql │ ├── setup.sh # 转发到 connector 的 setup 脚本 │ ├── test/schema.sql # 种子 schema(MySQL 为空占位) │ └── src/__tests__/ │ ├── mysql.datasource.ts # 环境变量 -> DataSourceOptions │ ├── mysql-default-repository.acceptance.ts │ └── mysql-repository-execute.acceptance.ts ├── repository-postgresql/ # 同构,另含真正的 schema.sql ├── repository-mongodb/ # 同构(无 test/ 目录) ├── repository-cloudant/ │ ├── setup.sh # 自包含的 CouchDB 启动脚本 │ ├── docker.setup.js # Node 脚本:PUT 创建数据库 + 重试 │ └── src/__tests__/ # cloudant-*.acceptance.ts / cloudant.datasource.ts └── extension-logging-fluentd/ ├── fixtures/ │ ├── etc/fluentd.conf # fluentd 服务配置 │ ├── start-fluentd.sh / stop-fluentd.sh │ └── ... └── src/__tests__/ ├── acceptance/fluent.acceptance.ts └── fixtures/fluentd.docker.ts # testcontainers 封装9. 小结与实践建议
- 验收测试包不参与根
npm test(根 mocha glob 只覆盖packages/extensions/examples),需要进入acceptance/<package>目录手动npm test; - 连接参数一律走环境变量,默认值同时体现在 README 与
*.datasource.ts中,二者可互相印证;修改实例信息时优先改环境变量而不是源码; - 没有本地数据库时优先用
setup.sh(Cloudant 用自研脚本 +ibmcom/couchdb3镜像),fluentd 包则完全自动化,只需 Docker 可用; - 想为新连接器补充验收测试时,最小改动就是新增一个包:写好
connector + 环境变量的*.datasource.ts、声明CrudFeatures,然后用crudRepositoryTestSuite一行接入全部标准 CRUD 验证——这正是 MySQL/PostgreSQL/MongoDB/Cloudant 四个包共同遵循的模式。
- 后端
- 微服务
【免费下载链接】loopback-next
LoopBack makes it easy to build modern API applications that require complex integrations.
相关推荐
Optimism 验收测试(Acceptance Tests)运行指南:op-acceptance-tests 全流程实战
Optimism 验收测试(Acceptance Tests)运行指南:op acceptance tests 全流程实战 本文以 Optimism 主仓库中的
区块链Web3后端终极指南:如何为pack CLI执行全面测试与验证
终极指南:如何为pack CLI执行全面测试与验证 pack是一款基于Cloud Native Buildpacks的强大CLI工具,它能帮助开发者轻松构建容器
Puppet Acceptance 测试完全指南:Beaker 验收测试的环境搭建、配置与运行实战
Puppet Acceptance 测试完全指南:Beaker 验收测试的环境搭建、配置与运行实战 导读 本文基于 puppet 仓库 acceptance/
运维DevOpsIaC
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考