MongoDB 测试指南:使用 resmoke 测试编排器运行与配置集成测试
2026/9/13 17:49:39 网站建设 项目流程

MongoDB 测试指南:使用 resmoke 测试编排器运行与配置集成测试

【免费下载链接】mongoThe MongoDB Database项目地址: https://gitcode.com/GitHub_Trending/mo/mongo

resmoke 是 MongoDB 仓库内置的测试运行器与编排工具,几乎所有 MongoDB 测试(尤其是jstests/下的 JavaScript 集成测试)都经由它调度执行。本文以 docs/testing/README.md 为主线,结合仓库内的 suites、fixtures、hooks、testcases 等配套文档与源码实现,系统讲解 resmoke 的四大核心概念(suites、fixtures、hooks、testcases)、从零跑通一个测试的完整流程,以及如何读懂并编写自定义测试套件配置。读完本文,你将能够独立使用 resmoke 运行指定测试文件、解释 suite YAML 的每个字段含义,并知道如何按需组合拓扑(fixture)与测试前后逻辑(hook)。

1. resmoke 是什么

resmoke 是 MongoDB 的集成测试运行器(integration test runner),其入口文件位于仓库根目录下的 buildscripts/resmoke.py。该脚本本身只是一个薄壳,主要作用是修正PYTHONPATH后调用buildscripts.resmokelib.cli模块(对应 buildscripts/resmokelib/cli.py)的main()函数,再由parser.parse_command_line()解析子命令并执行。

resmoke 负责的工作远不止“执行一条命令”这么简单,它实际上是一个测试编排器(orchestrator),需要协调:

  • 按 suite 配置挑选(select)要运行的测试文件;
  • 按 fixture 配置拉起对应的服务器拓扑(如独立 mongod、副本集、分片集群);
  • 按 hooks 配置在测试前后执行数据校验、清理等附加逻辑;
  • 汇总测试结果、在失败时归档数据文件(archive)供事后分析。

值得注意的是,尽管 MongoDB 源码本身使用 Bazel 构建,但 resmoke尚未与 Bazel 集成,因此运行测试前需要先用bazel build产出可执行二进制(详见 buildscripts/resmokelib/README.md 的 Build 一节)。

2. 四大核心概念

原文档明确将 resmoke 的知识体系拆成四个独立概念,每个都有自己专门的文档。它们是理解 resmoke 的钥匙:

  • suites(套件):决定“跑哪些测试、怎么跑”,本质是 YAML 配置文件。详见 buildscripts/resmokeconfig/suites/README.md;
  • fixtures(夹具):指定测试所针对的服务器拓扑(独立节点、副本集、分片集群等)。详见 buildscripts/resmokelib/testing/fixtures/README.md;
  • hooks(钩子):在单个测试之前、之后以及整个 suite 前后运行的逻辑(如校验数据一致性、周期性清理)。详见 buildscripts/resmokelib/testing/hooks/README.md;
  • testcases(测试用例类型):resmoke 可以运行的不同“种类”的测试,本质是 Pythonunittest.TestCase的扩展。详见 buildscripts/resmokelib/testing/testcases/README.md。

下面分别展开。

2.1 Suites:测试如何被分组与配置

Suite 是一份 YAML 文件,文件名即 suite 名,其作用是把“要跑的测试文件、fixture 及其参数、shell 选项、hooks、归档策略”打包成一个可复用的配置单元。所有 suite 配置位于 buildscripts/resmokeconfig/suites/ 目录(约 940 个 YAML 文件)。

一个最精简的 suite 只需要三部分:

test_kind: js_test selector: roots: - jstests/mytests/**/*.js executor: config: shell_options: nodb: ""

含义是:suite 名即文件名my_suite;包含jstests/mytests目录下所有 JS 文件;这些测试在一个传入nodb: ""选项的 shell 中运行。

下面用一个带占位符的完整结构示例展示 suite 的整体骨架(摘自 buildscripts/resmokeconfig/suites/README.md):

test_kind: js_test selector: roots: - jstests/mytests/**/*.js executor: config: shell_options: nodb: "" global_vars: TestData: defaultReadConcernLevel: null hooks: - class: ValidateCollections - class: CleanEveryN n: 20 fixture: class: ShardedClusterFixture num_shards: 2 archive: tests: true hooks: - ValidateCollections
2.1.1test_kind

声明该 suite 中测试的种类,常见值如js_testcpp_unit_testpy_testfsm_workload_test等。全部受支持的类型见 buildscripts/resmokelib/testing/testcases/README.md。其中js_test覆盖了大约75% 的 suite,是绝对主力。

2.1.2selector:选择测试文件

selector 决定 suite 包含/排除哪些测试文件,主要字段:

  • roots:要包含的测试文件路径列表,支持 glob。如果提供的是不带 glob 的路径,则该文件必须真实存在
  • root:一个文件(通常为build/unittests.txt),内部每行一个 glob 模式,常用于cpp_unit_test类型,指定候选测试;
  • include_files:glob 列表,只保留这部分测试;即使这些文件被 tag 排除了也会被强制包含;若指定了 roots 中不存在的测试会报错;
  • exclude_files:glob 列表,排除这些测试;即使 tag 允许也会被排除;若指定的测试不在 roots 中会报错;
  • include_with_any_tags:只保留定义了任一这些 tag 的 jstest;
  • exclude_with_any_tags:排除定义了任一这些 tag 的 jstest(除非文件名层面被强制包含)。

示例如下:

selector: roots: - jstests/aggregation/**/*.js exclude_files: - jstests/aggregation/extras/*.js - jstests/aggregation/data/*.js exclude_with_any_tags: - requires_pipeline_optimization

想查看仓库中所有被引用的 tag 及其说明,可运行./buildscripts/resmoke.py list-tags

2.1.3executor:定义如何执行

executor下分四块:confighooksfixturearchive

executor.config提供每个测试的附加配置,结构随test_kind变化(具体以 buildscripts/resmokelib/testing/testcases/ 下对应实现为准)。对最常见的js_test而言,核心是shell_options

config: shell_options: global_vars: TestData: defaultReadConcernLevel: null nodb: "" gssapiServiceName: "mockservice" eval: >- var testingReplication = true; load('jstests/libs/override_methods/set_read_and_write_concerns.js'); load('jstests/libs/override_methods/enable_causal_consistency_without_read_pref.js');
  • shell_options中除global_vars外的参数会原样传给 mongo shell 可执行文件;标志型参数需要写成''空值,如nodb: ""
  • global_vars作为传给--eval的字符串基础(会做对象格式化),eval字段指定的 JS 会追加在其后;
  • TestData是一个特殊的全局变量,用于承载测试数据。它的取值合并优先级为:resmoke 命令行 > suite.yml> 运行时/默认值
  • eval可以直接写 JS 代码,也可以把 JS 放进独立脚本后用load(...)加载。

executor.hooks声明要在测试前后运行的钩子。yml 中的class必须与 hook 的 Python 类名一致,其余字段作为参数传入构造函数(hook_loggerfixture由 resmoke 自动注入,不要写在 yml 里)。示例:

hooks: - class: CheckReplOplogs - class: CheckReplDBHash - class: ValidateCollections - class: CleanEveryN n: 20 - class: MyHook param1: something param2: somethingelse

executor.fixture指定测试所运行的拓扑。class对应 fixture 的 Python 类名,其余字段传给构造函数:

fixture: class: ShardedClusterFixture num_shards: 2 mongos_options: bind_ip_all: "" set_parameters: enableTestCommands: 1 mongod_options: bind_ip_all: "" set_parameters: enableTestCommands: 1 periodicNoopIntervalSecs: 1 writePeriodicNoops: true

executor.archive配置失败时的数据归档(上传到 S3)。当某个 hook 或 test 抛异常即视为失败,触发归档的时机有二:归档列表中的任意 hook 抛异常;或tests: true时 suite 中任意 test 抛异常。示例:

archive: hooks: - Hook1 - Hook2 tests: true

archive.hooks是 hook 类名列表(设为true表示归档所有 hook);archive.tests是测试文件列表(支持通配,true表示归档所有测试)。关于失败归档的更多机制可阅读 buildscripts/resmokeconfig/suites/README.md 与 buildscripts/resmokelib/testing/testcases/README.md 中对FixtureAbortTestCase的说明。

2.2 Fixtures:测试针对的服务器拓扑

Fixture 定义测试所针对的具体拓扑,在 suite 的fixture字段中声明。仓库支持的 fixture 包括(均位于 buildscripts/resmokelib/testing/fixtures/):

  • MongoDFixture(standalone.py):提供独立 mongod;
  • ReplicaSetFixture(replicaset.py):副本集;
  • ShardedClusterFixture(shardedcluster.py):分片集群,同时承担“由 JS 测试自身通过MongoRunnerReplSetTestShardingTest启动部署”的场景;
  • MultiReplicaSetFixture(multi_replica_set.py)、MultiShardedClusterFixture(multi_sharded_cluster.py):多组副本集/分片集群;
  • BulkWriteFixture(bulk_write.py):向 JSTest 提供一组集群;
  • ExternalFixture(external.py)与ExternalShardedClusterFixture:连接外部(非 resmoke 管理)的集群;
  • MongoTFixture(mongot.py):mongod 旁边再拉起一个 mongot;
  • YesFixture(yesfixture.py):生成大量日志消息的辅助 fixture。

fixture 侧还有一组接口类(interface.py):Fixture是所有 fixture 的基类;MultiClusterFixture是可由多个独立集群组成的基类(参与者集群平时独立运行,仅在参与迁移等过程时被绑定);NoOpFixture不启动任何服务器;ReplFixture是所有支持复制的 fixture 的基类。

2.3 Hooks:测试边界上的运行逻辑

Hook 是在测试内容边界(before/after test、before/after suite)上运行的例程。suite 的hooks字段中可声明任意组合。仓库支持的 hooks 数量很多,按用途可粗略分为几类(详见 buildscripts/resmokelib/testing/hooks/README.md):

  • 数据一致性校验:如CheckReplDBHash(dbhash.py)对比主从 dbhash;CheckReplOplogs(oplog.py)检查local.oplog.rs在主备上一致;CheckReplPreImagesConsistency检查config.system.preimagesValidateCollections(validate.py)做完整校验;CheckOrphansDeleted(orphans.py)检查孤儿文档是否删除干净;
  • 后台扰动/故障注入ContinuousStepdown(stepdown.py)周期性发送replSetStepDownPeriodicKillSecondaries(periodic_kill_secondaries.py)周期性杀掉副本集 secondary;LagOplogApplicationInBackground(secondary_lag.py)制造 secondary 回放延迟;FuzzRuntimeParameters(fuzz_runtime_parameters.py)周期性下发随机setParameterHelloDelays(hello_failures.py)注入 Hello 故障;
  • 初始同步相关BackgroundInitialSync/IntermediateInitialSync(initialsync.py),使用前提是 ReplicaSetFixture 以start_initial_sync_node=True启动,且与CleanEveryN共用时n要一致;
  • 清理与重启CleanEveryN(cleanup.py)每跑n个测试重启 fixture;DropUserCollectionsCleanupConcurrencyWorkloads(cleanup_concurrency_workloads.py);
  • 变更流/后台任务RunChangeStreamsInBackground(change_streams.py)在后台跑全集群 change stream;RunDBCheckInBackground(dbcheck_background.py)后台跑dbCheckRunQueryStats(run_query_stats.py)每个测试后运行$queryStats
  • 崩溃模拟与归档SimulateCrash(simulate_crash.py);MagicRestoreEveryN(magic_restore.py)依赖MagicRestoreFixture

所有 hooks 都继承自buildscripts.resmokelib.testing.hooks.interface.Hook(interface.py),并可覆写以下空方法中的任意子集:

  • before_suite
  • before_test
  • after_test
  • after_suite

至少要覆写其中一个方法,否则 hook 什么都不做。常见的自定义工作包括校验数据、删除数据、执行清理等。此外还有三个接口层:JSHook(jsfile.py)是携带静态 JS 文件的 hook 接口,DataConsistencyHook是其上用于数据一致性检查的封装(shell 以非零码退出时抛errors.ServerFailure终止测试);BGHook(bghook.py)会在后台线程中反复调用run_action(),贯穿整个 suite 生命周期;PerClusterDataConsistencyHook则在 fixture 的每个独立集群上运行。

2.4 Testcases:测试的“种类”

TestCases 是 Pythonunittest.TestCase的扩展,resmoke 以不同“种类”(test_kind)运行它们。完整清单见 buildscripts/resmokelib/testing/testcases/README.md,此处列举代表性类型:

  • js_test(jstest.py):JS 集成测试,约 75% 的 suite 使用,具体编写规范见 jstests/README.md;
  • all_versions_js_testjs_test的别名,用于多版本(multiversion)透传 suite,会以副本集/分片集群的所有版本组合运行;
  • cpp_unit_test(cpp_unittest.py):C++ 单元测试;cpp_integration_testcpp_libfuzzer_test对应 C++ 集成与 libfuzzer 测试;
  • fsm_workload_test/parallel_fsm_workload_test(fsm_workload_test.py):并发(FSM)工作负载测试;
  • py_test(pytest.py):Python 测试;
  • benchmark_testjson_schema_testsdam_json_testserver_selection_json_testsleep_testtla_plus_test等各司其职。

接口层面,顶层有TestCase(必须实现run_test)与ProcessTestCase(执行外部进程,必须实现_make_process)(均在 interface.py);子类包括JSRunnerFileTestCase(jsrunnerfile.py)与MultiClientsTestCase(多个单用例副本的封装)以及TestCaseFactory工厂。

一个值得注意的机制是Fixture TestCases:resmoke 内部通过FixtureTestCaseManager用测试用例来协调 fixture 生命周期——suite 会先跑一个FixtureSetupTestCase建好拓扑,再跑你的 N 个测试,最后跑FixtureTeardownTestCase拆除。因此一次运行中你会看到N+2个“测试”通过,多出的两个正是 fixture 的 setup 与 teardown。另外,当测试失败且配置了归档时,resmoke 会动态生成一个FixtureAbortTestCase立即执行,向每个 mongod 发送SIGABRT以便在归档前捕获崩溃现场。

3. 动手实践:从零跑通一个测试

3.1 准备 Python 虚拟环境

先确保 venv 已激活且依赖最新:

python3 -m venv python3-venv source python3-venv/bin/activate buildscripts/uv_sync.sh

uv_sync.sh是仓库提供的依赖同步脚本,位于 buildscripts/uv_sync.sh,它会按仓库锁定的版本安装 Python 依赖。如果直接运行buildscripts/resmoke.py而 venv 未激活(且不在 Bazel workspace 环境下),buildscripts/resmoke.py 会提示 "You need to activate your virtual environment"。

3.2 构建被测二进制

由于 resmoke 尚未与 Bazel 集成,需要先用 Bazel 构建可测试的安装产物:

bazel build install-dist-test

3.3 运行单个测试文件

以原文档中的示例为例,从单个测试文件运行测试内容:

buildscripts/resmoke.py run --suites=no_passthrough jstests/noPassthrough/shell/js/string.js

这条命令做了这些事:

  • 通过run子命令(resmoke 最常用的功能)执行测试;
  • --suites=no_passthrough指定使用 buildscripts/resmokeconfig/suites/no_passthrough.yml 中的 suite 配置;
  • 命令行给出的 JS 文件 jstests/noPassthrough/shell/js/string.js 必须落在该 suite 的roots通配范围内。

no_passthroughsuite 的特点是:“passthrough”指用不同的运行时集群配置(拓扑、运行时参数、故障注入等)来跑同一测试,大多数测试都能在 passthrough suite 中运行;而 noPassthrough 是例外——这些测试只在测试自身预定义的精确配置下运行(见 buildscripts/resmokeconfig/suites/no_passthrough.yml 的description)。它的selector.roots通过 glob 覆盖了jstests/noPassthrough/**/*.jsjstests/concurrency/*.js等目录,exclude_files排除了jstests/noPassthrough/libs/**/*.js这类库文件,并通过exclude_with_any_tags排除requires_kernel_619requires_kernel_7014等需要特定内核的测试。

它的 executor 配置非常精简——原文档明确指出:不指定 fixture、不指定 hooks,executor 只带一份最小配置nodb: ""让 shell 以不连接数据库的模式启动,并关闭测试扩展签名校验的 server parameter)。这正说明这些 noPassthrough 测试由 JS 代码自己启动 mongod(MongoRunner等),suite 层无需再管理拓扑。

被执行的 jstests/noPassthrough/shell/js/string.js 是一个用 mochalite 编写的 String shim/polyfill 测试,包含trimtrimLefttrimRightltrimrtrimstartsWithendsWithincludespad等用例——非常适合作为入门跑通流程的最小样例。

3.4 run 子命令与常用参数

run子命令有100+ 个 flag。resmoke 刻意不在文档中重复罗列全部参数,以免多源信息漂移过期,统一以buildscripts/resmoke.py run --help为准。以下是高使用频率参数的说明(源自 buildscripts/resmokelib/README.md):

--suites/--suiterun既可以运行 suite(一组测试 + 对应的拓扑与配置),也可以运行显式指定的测试文件。单套件用--suite,多套件用逗号分隔传给--suites

--installDirresmoke 可以针对任意“可测试安装”(ASAN、Debug、Release 等)运行测试。当本地构建被安装到 git 仓库根目录的子目录、且仓库内恰好只有一份构建时,resmoke 能自动定位并使用本地构建;其他情况则用--installDir显式指定 mongod/mongos 二进制所在目录。替代方案是直接使用与 mongod 二进制同目录下的resmoke.py包装脚本,它会自动替你设置installDir。注意:该包装脚本在打包安装(如 Homebrew 等包管理器提供的发行版)中不存在,此时必须显式传--installDir

其他子命令(摘录自resmoke --help):

  • list-suites:列出可执行的 suite 名;
  • find-suites:列出会执行指定测试的 suite;
  • generate-matrix-suites:从映射文件生成 matrix suite 配置;
  • list-tags:列出 suites 中可用的 tag 及说明;
  • generate-multiversion-exclude-tags:基于当前分支与 last-lts/last-continuous 分支上BACKPORTS_REQUIRED_FILE的对比,生成多版本测试的排除 tag 文件;
  • test-discovery:发现 suite 会运行哪些测试;
  • suiteconfig:显示 suite 的配置;
  • core-analyzer:分析指定输入文件的 core dump;
  • hang-analyzer:Evergreen 集成的原型挂起分析器,支持抓取 dump 与进程信息摘要;
  • powercycle:断电循环测试脚本;
  • generate-fuzz-config:用配置模糊器生成 mongod.conf 与 mongos.conf;
  • generate-fcv-constants等其他内部命令。

另外注意:bisectsetup-multiversionsymbolize三个子命令已迁移到db-contrib-tool工具,不再由 resmoke 提供。

4. 深入原理:resmoke 的命令行入口与参数校验

从源码看,resmoke 的入口链路是:

  1. buildscripts/resmoke.py 修正PYTHONPATH后调用cli.main(sys.argv)
  2. buildscripts/resmokelib/cli.py 的main()记录进程号到环境变量(RESMOKE_PARENT_PROCESSRESMOKE_PARENT_CTIME,供子进程识别父进程),若由bazel run调用则切到 workspace 根目录,随后交给parser.parse_command_line()解析并执行对应子命令;
  3. 参数解析与校验位于buildscripts/resmokelib/parser模块与 buildscripts/resmokelib/configure_resmoke.py 的_validate_options()中。其中可以看到若干对用户友好的校验逻辑,例如:
    • --executor已被--suites取代,若使用会直接报错并提示改用--suites={} {}
    • 命令行测试文件列表与--replayFile不能同时使用;
    • --shellSeed必须配合且仅配合一个测试文件使用;
    • 指定的测试文件若不存在(且不以@开头表示 replay 文件)会报 "Test file ... does not exist"。

5. 遥测:resmoke 的 OpenTelemetry 埋点

resmoke 使用 OpenTelemetry(OTel)采集自身运行的指标,用于 Evergreen CI 场景下的性能优化。每次 resmoke 调用都会采集,但只有运行在 Evergreen 时数据才会上报到 Honeycomb(详见 docs/testing/otel_resmoke.md)。采集内容包括:

  1. 一个 resmoke suite(一组 JS 测试)的运行时长;
  2. suite 内每个测试(单个 JS 测试)的运行时长;
  3. 测试/suite 前后 hooks 的耗时;
  4. resmoke 归档器(失败时归档 core dump)的相关数据。

实现上,大部分配置集中在_set_up_tracing(...)方法(buildscripts/resmokelib/configure_resmoke.py),并配套了BatchedBaggageSpanProcessor(buildscripts/resmokelib/utils/batched_baggage_span_processor.py)与FileSpanExporter(buildscripts/resmokelib/utils/file_span_exporter.py)两个自定义组件。数据采集以装饰器为主,例如(取自 buildscripts/resmokelib/testing/job.py):

TRACER = trace.get_tracer("resmoke") @TRACER.start_as_current_span("func_name") def func_name(...): span = trace.get_current_span() span.set_attribute("attr1", True)

装饰器方式可以自动捕获异常、保证 span 一定被关闭;个别场景也会用with块手动开 span,但装饰器是首选方案。

6. 相关文档导航

  • 测试运行器总览:buildscripts/resmokelib/README.md
  • Suite 配置详解:buildscripts/resmokeconfig/suites/README.md
  • Fixture 与拓扑:buildscripts/resmokelib/testing/fixtures/README.md
  • Hook 机制:buildscripts/resmokelib/testing/hooks/README.md
  • TestCase 种类:buildscripts/resmokelib/testing/testcases/README.md
  • JS 测试编写规范:jstests/README.md
  • resmoke 遥测:docs/testing/otel_resmoke.md
  • 并发测试框架:docs/testing/fsm_concurrency_testing_framework.md
  • 挂起分析:docs/testing/hang_analyzer.md

【免费下载链接】mongoThe MongoDB Database项目地址: https://gitcode.com/GitHub_Trending/mo/mongo

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询