当前位置: 首页 > news >正文

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

两个工具各做什么
工具 全称 核心职责
tox tox 标准化的虚拟环境管理 + 测试编排
testr testrepository 测试仓库管理、并行执行、结果持久化
一句话概括:

tox 解决的是「在干净的虚拟环境里跑测试,避免依赖污染」。
testr 解决的是「几十万测试怎么并行跑、跑完的结果如何归档复用」。
tox
↓ 创建/复用虚拟环境
↓ 执行 commands
python 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 执行步骤

  1. 解析 tox.ini,确定要运行的 env(默认 [testenv],或 -e 指定)
  2. 在 .tox// 下创建 venv
  3. 安装 deps = … 列出的依赖
  4. 安装项目本身(pip install .)
  5. 执行 commands = … 中的命令
  6. 收集退出码,汇总结果
    2.3 典型 tox.ini
    [tox]
    envlist = py39,py311,pep8,cover
    skipsdist = False

[testenv]
usedevelop = True
setenv =
VIRTUAL_ENV={envdir}
LANG=en_US.UTF-8
deps =
-r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
commands =
python -m testr run {posargs}

[testenv:pep8]
deps = flake8
commands = flake8 {toxinidir}/myproject

[testenv:cover]
setenv =
{[testenv]setenv}
PYTHON=coverage run --source myproject -m testr run
commands =
coverage erase
{env:PYTHON}
coverage report
coverage html
注意 commands = python -m testr run —— 这就是 tox 把测试执行权交给 testr 的关键。

[tox]
envlist = py27,cover
skipsdist = False

[testenv]
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
testrepository
subunit

[testenv:cover]
setenv = VIRTUAL_ENV={envdir}
NOSE_WITH_COVERAGE=1
commands =
python setup.py testr --slowest --coverage
–testr-args=“–subunit”

tox -ecover --sitepackages -v 各参数含义:

参数 含义
-e cover 选择执行 cover 环境
–sitepackages 复用系统已安装的包,不重新虚拟化所有依赖
-v 详细输出
三、testr 工作原理
3.1 核心概念:testrepository
testr 在项目根目录维护一个 .testr/ 目录,里面存放:

.testr/
├── config # testr 配置
├── last # 上次运行的 subunit 流
├── slowest # 最慢测试列表
├── failing # 最近失败的测试
└── history # 历史运行记录
这是一个持久化的测试仓库,不只是一次性的执行框架。

3.2 subunit 协议
testr 的核心数据格式是 subunit —— 一种二进制/文本混合的测试结果流协议,兼容 xUnit 但更丰富。

test: mypackage.tests.test_foo.TestFoo.test_bar
success: 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_DIR
test_list_command = python -m subunit.run discover -t . $TESTS_DIR --list
group_regex = ([^.]+.)
test_run_concurrency = 1
test_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,确定运行 env=py311 │
└─────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│ Step 2: tox 在 .tox/py311/ 创建虚拟环境 │
│ (若已存在且无 skip-install,直接复用) │
└─────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│ Step 3: pip install -r requirements.txt │
│ pip install -r test-requirements.txt │
│ pip install -e . (项目本身,usedevelop=True) │
└─────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│ 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 失败,汇总到最终结果 │
└─────────────────────────────────────────────────────────────┘

http://www.jsqmd.com/news/1217843/

相关文章:

  • 用 AI 做视频拉片:镜头切分与结构化分镜数据的自动化实践
  • 深入解析TI IVA2.2 EDMA TPTC寄存器:从原理到实战配置
  • 求大佬帮忙
  • 深入解析MMC/SD/SDIO主机控制器驱动开发:从寄存器配置到实战调试
  • Cursor、Codex、Claude 代码评审、循环迭代编码(Loop Coding)完整开发流程拆解
  • 宝珀中国官方专柜客户服务热线权威信息声明(2026年7月最新) - 宝珀官方售后服务中心
  • Unity Meta Quest开发:Tracking Origin Type原理与实战配置指南
  • Nginx主动防御配置实战:构建Cloudflare后的第二道安全防线
  • 3个简单步骤彻底告别键盘连击烦恼:让老旧机械键盘重获新生
  • 安卓网络请求框架android-async-http详解与优化实践
  • 智慧食堂系统源码开发指南:采购、库存、配送、结算一体化平台如何实现?
  • 嵌入式系统看门狗定时器原理与TI OMAP实战配置详解
  • 嵌入式Linux与AI Agent开发:技术对比、薪资分析与学习路径
  • 开发必备:常用模块分类解析与最佳实践
  • Meshroom 3D重建免费软件:从零开始创建专业级三维模型的完整指南
  • 每天只有半小时复盘,量化软件先比较提醒准确率和处理成本
  • Kaggle竞赛四维解构法:指标、数据结构、时间逻辑与算力约束
  • 快速理解js原型链
  • HX4004A-MFC开关电容升压芯片:锂电池至5V转换实战指南
  • 程序员不会消失,但“只会写代码”的程序员会消失
  • 2026年最新教程:几张图片怎么竖着拼在一起 亲测免安装的实用方法 - 玩机日常
  • Unity游戏无声问题排查:从AudioSource到AudioListener的完整解决方案
  • IPXWrapper:让经典游戏在现代Windows上重获网络对战能力的终极指南
  • Android地图定位功能实现与优化指南
  • Windows C++ DLL开发:__declspec(dllexport)原理、用法与跨模块编程实践
  • Android日志封装实践:从基础到高级优化
  • MT5与Python集成:金融数据分析与量化交易实战指南
  • 2026年南康实木套系家具源头工厂哪家可靠,榫卯结构新中式家具/新中式家具/全屋定制家具,实木套系家具品牌厂哪家强 - 品牌推荐师
  • Visual C++构建高性能GIS系统:MFC架构与实战优化指南
  • 论文既要降重又要降AI怎么办?一次两项检测都降到合格