tox + testr 单元测试工作流程详解:从环境隔离到并行执行

发布时间:2026/7/19 5:31:31

tox + testr 单元测试工作流程详解:从环境隔离到并行执行 两个工具各做什么工具 全称 核心职责tox tox 标准化的虚拟环境管理 测试编排testr testrepository 测试仓库管理、并行执行、结果持久化一句话概括:tox 解决的是「在干净的虚拟环境里跑测试,避免依赖污染」。testr 解决的是「几十万测试怎么并行跑、跑完的结果如何归档复用」。tox↓ 创建/复用虚拟环境↓ 执行 commandspython setup.py testr --slowest --coverage --testr-args“–subunit”↓ 调用 pbr 的 testr 入口testr run↓ 读取 .testr.conf↓ 根据 test_command 启动测试进程python -m subunit.run discover -t ./ ./tests/unit↓ 执行测试用例输出 subunit v1 流↓ testr 捕获流↓ 写入 .testrepository/{next_number}↓ 合并历史结果testr last --subunit↓ 输出最新一次运行的 subunit 流subunit-trace --no-failure-debug -f↓ 转换为人类可读的实时输出两者解耦 —— tox 可以单独跑 pytest/nose,testr 也可以独立于 tox 使用。组合起来就形成「环境隔离 高性能并行 结果仓库」的完整闭环。二、tox 工作原理2.1 核心机制tox 读取 tox.ini(或 pyproject.toml 中的 [tool.tox]),按其中定义的 testenv 创建虚拟环境、安装依赖、执行命令。tox.ini├── [tox] 全局配置├── [testenv] 默认环境模板├── [testenv:py311] 具名环境:Python 3.11├── [testenv:pep8] 具名环境:代码风格检查└── [testenv:cover] 具名环境:覆盖率2.2 执行步骤解析 tox.ini,确定要运行的 env(默认 [testenv],或 -e 指定)在 .tox// 下创建 venv安装 deps … 列出的依赖安装项目本身(pip install .)执行 commands … 中的命令收集退出码,汇总结果2.3 典型 tox.ini[tox]envlist py39,py311,pep8,coverskipsdist False[testenv]usedevelop Truesetenv VIRTUAL_ENV{envdir}LANGen_US.UTF-8deps -r{toxinidir}/requirements.txt-r{toxinidir}/test-requirements.txtcommands python -m testr run {posargs}[testenv:pep8]deps flake8commands flake8 {toxinidir}/myproject[testenv:cover]setenv {[testenv]setenv}PYTHONcoverage run --source myproject -m testr runcommands coverage erase{env:PYTHON}coverage reportcoverage html注意 commands python -m testr run —— 这就是 tox 把测试执行权交给 testr 的关键。[tox]envlist py27,coverskipsdist False[testenv]deps -r{toxinidir}/requirements.txt-r{toxinidir}/test-requirements.txttestrepositorysubunit[testenv:cover]setenv VIRTUAL_ENV{envdir}NOSE_WITH_COVERAGE1commands python setup.py testr --slowest --coverage–testr-args“–subunit”tox -ecover --sitepackages -v 各参数含义参数 含义-e cover 选择执行 cover 环境–sitepackages 复用系统已安装的包不重新虚拟化所有依赖-v 详细输出三、testr 工作原理3.1 核心概念:testrepositorytestr 在项目根目录维护一个 .testr/ 目录,里面存放:.testr/├── config # testr 配置├── last # 上次运行的 subunit 流├── slowest # 最慢测试列表├── failing # 最近失败的测试└── history # 历史运行记录这是一个持久化的测试仓库,不只是一次性的执行框架。3.2 subunit 协议testr 的核心数据格式是 subunit —— 一种二进制/文本混合的测试结果流协议,兼容 xUnit 但更丰富。test: mypackage.tests.test_foo.TestFoo.test_barsuccess: mypackage.tests.test_foo.TestFoo.test_bar (1.234s)特点:流式传输,边跑边出结果支持多进程汇流(并行测试的多个 worker 写入同一流)兼容 Python unittest、pytest、nose 等主流框架3.3 .testr.conf[DEFAULT]test_command python -m subunit.run discover -t . $TESTS_DIRtest_list_command python -m subunit.run discover -t . $TESTS_DIR --listgroup_regex ([^.].)test_run_concurrency 1test_command:实际执行测试的命令,subunit.run 是 subrun 的入口,负责输出 subunit 流test_list_command:列出所有可用测试 ID(用于过滤、并行分片)group_regex:把测试按前缀分组,用于并行调度test_run_concurrency并行度3.4 核心参数详解python setup.py testr --slowest --coverage --testr-args“–subunit”参数 含义–slowest 测试结束后显示最慢的 10 个用例–coverage 自动启用 coverage生成 .coverage 文件–testr-args“–subunit” 透传给 testr run让 testr 输出 subunit 流–coverage 的工作机制testr 在执行前向 test_command 注入 coverage 包装等价于 python -m coverage run --source -m subunit.run discover …每个测试进程会写自己的 .coverage.{pid} 文件后续脚本 coverage combine 合并所有进程的覆盖率数据四、完整工作流程把 tox 和 testr 串起来,一次 tox -e py311 的内部流程:┌─────────────────────────────────────────────────────────────┐│ Step 1: tox 解析 tox.ini,确定运行 envpy311 │└─────────────────────────────────────────────────────────────┘│▼┌─────────────────────────────────────────────────────────────┐│ Step 2: tox 在 .tox/py311/ 创建虚拟环境 ││ (若已存在且无 skip-install,直接复用) │└─────────────────────────────────────────────────────────────┘│▼┌─────────────────────────────────────────────────────────────┐│ Step 3: pip install -r requirements.txt ││ pip install -r test-requirements.txt ││ pip install -e . (项目本身,usedevelopTrue) │└─────────────────────────────────────────────────────────────┘│▼┌─────────────────────────────────────────────────────────────┐│ Step 4: 执行 commands: python -m testr run ││ 此刻控制权从 tox 转交给 testr │└─────────────────────────────────────────────────────────────┘│▼┌─────────────────────────────────────────────────────────────┐│ Step 5: testr 读取 .testr/config 获取 test_command ││ 读取 .testr/history 准备 failing-first 调度 │└─────────────────────────────────────────────────────────────┘│▼┌─────────────────────────────────────────────────────────────┐│ Step 6: testr 调用 test_list_command 列出全部测试 ID ││ 按 group_regex 切分,分发给 N 个并行 worker │└─────────────────────────────────────────────────────────────┘│▼┌─────────────────────────────────────────────────────────────┐│ Step 7: 每个 worker 执行: ││ python -m subunit.run discover -t . $TESTS_DIR test_ids ││ 输出 subunit 流到 stdout │└─────────────────────────────────────────────────────┬───────┘│┌───────────────────────────────────────────────┘│ (多个 worker 的 subunit 流汇入)▼┌─────────────────────────────────────────────────────────────┐│ Step 8: testr 把 subunit 流 tee 到: ││ - .testr/last (上次完整流) ││ - .testr/history (累积历史,供下次调度) ││ - 实时渲染到终端 (subunit2py 或 subunit2html) │└─────────────────────────────────────────────────────────────┘│▼┌─────────────────────────────────────────────────────────────┐│ Step 9: testr 统计 pass/fail/skip,以 exit code 返回 │└─────────────────────────────────────────────────────────────┘│▼┌─────────────────────────────────────────────────────────────┐│ Step 10: tox 接收到 exit code, ││ 非 0 则标记 env 失败,汇总到最终结果 │└─────────────────────────────────────────────────────────────┘

相关新闻