🤗 Transformers 测试指南:从 CI 流水线到测试编写实战
【免费下载链接】transformers🤗 Transformers: the model-definition framework for state-of-the-art machine learning models in text, vision, audio, and multimodal models, for both inference and training.项目地址: https://gitcode.com/GitHub_Trending/tra/transformers
本文是 🤗 Transformers 仓库官方日语版《Testing》文档的深度解读与实战指南,系统梳理了该开源仓库的两套测试套件(tests与examples)、CI 测试触发机制、pytest 全场景运行技巧,以及基于unittest/pytest的测试编写规范。读完本文,你将掌握如何选择与运行特定测试、如何用@slow、@require_*等装饰器管理测试等级与硬件依赖、如何利用TestCasePlus与CaptureStdout等测试工具类编写健壮的测试用例,并能正确参与仓库的 CI 协作流程。
了解 🤗 Transformers 的测试体系
两套测试套件
本仓库包含两套测试套件,职责划分明确:
tests—— 针对通用 API 的测试,覆盖模型、分词器、Pipeline、Trainer 等核心组件;examples—— 针对各类应用场景(不属于 API 本体)的测试,例如 examples/pytorch 下的语言建模、文本分类、翻译等示例脚本。
CI 如何测试 transformers
PR 提交后,仓库会通过 CI 对代码进行多维度验证,主要分为两类:
- CircleCI:PR 每次提交新 commit 都会触发 9 个 CircleCI 作业重新测试。这些作业不会执行
@slow标记的慢测试; - GitHub Actions:另有三条工作流:
- torch hub 集成检查:验证 torch hub 的集成是否正常工作;
- self-hosted(push):当
main分支有新 commit 且只更新了src、tests、.github目录时,在 GPU 上运行快速测试; - self-hosted runner:在 GPU 上按固定计划运行
tests与examples的常规测试和慢测试,等价于:
RUN_SLOW=1 pytest tests/ RUN_SLOW=1 pytest examples/正因为慢测试只在定时 CI 中执行、不参与 PR 检查,某些问题可能在 PR 合并后才暴露——这也意味着提交 PR 前在本机跑一遍慢测试非常重要。
运行测试的实用命令
选择要运行的测试
最基础的两种方式——运行全部测试:
pytest或者使用仓库根目录 Makefile 中定义的便捷目标:
make test当前仓库的make test实际展开为(比文档撰写时多了随机顺序与插件启用参数):
python -m pytest -p random_order -n auto --dist=loadfile -s -v --random-order-bucket=module ./tests/各参数含义如下:
-n auto:启动与 CPU 核心数相同数量的测试进程(注意:内存不足时需谨慎);--dist=loadfile:同一文件内的所有测试在同一个测试进程中执行;-s:不捕获输出(关闭输出捕获);-v:冗长(verbose)模式;-p random_order与--random-order-bucket=module:启用 [pytest-random-order] 插件,按模块粒度随机化测试顺序,帮助暴露测试间的隐式耦合。
如需运行示例套件,可使用 Makefile 中的make test-examples(对应./examples/pytorch/目录)。
获取全部测试列表
收集整个测试套件中的所有测试:
pytest --collect-only -q收集指定测试文件中的所有测试(以优化器测试为例,当前仓库位于 tests/optimization/test_optimization.py):
pytest tests/optimization/test_optimization.py --collect-only -q运行特定测试模块
pytest tests/utils/test_logging.pytests/utils/test_logging.py是文档中反复出现的示例文件,其中定义了HfArgumentParserTest测试类以及test_set_level、test_env_override等用例,涵盖日志等级设置与环境变量覆盖行为,非常适合作为上手练习。
运行特定测试
大多数测试基于unittest,因此精确定位单个子测试需要知道其所在的 unittest 类名。例如:
pytest tests/optimization/test_optimization.py::OptimizationTest::test_adam_w其含义为:测试文件tests/optimization/test_optimization.py→ 类OptimizationTest→ 函数test_adam_w。若文件中包含多个类,也可以只运行某个类的全部测试:
pytest tests/optimization/test_optimization.py::OptimizationTest想查看类内有哪些测试:
pytest tests/optimization/test_optimization.py::OptimizationTest --collect-only -q用-k关键字表达式筛选
-k支持按测试名称的关键字子串过滤,并可组合and、or、not:
# 只运行名称含 "adam" 的测试 pytest -k adam tests/optimization/test_optimization.py # 运行名称不含 "adam" 的所有测试 pytest -k "not adam" tests/optimization/test_optimization.py # 名称同时含 "ada" 且不含 "adam" pytest -k "ada and not adam" tests/optimization/test_optimization.py # 同时运行 test_adam_w 与 test_adafactor(用 or 取并集) pytest -k "test_adam_w or test_adafactor" tests/optimization/test_optimization.py # 只包含同时匹配两个模式的测试(用 and 取交集) pytest -k "test and ada" tests/optimization/test_optimization.py运行 accelerate 测试
某些模型需要额外验证在accelerate下的行为。例如对 OPT 模型,只需追加-m accelerate_tests:
RUN_SLOW=1 pytest -m accelerate_tests tests/models/opt/test_modeling_opt.py文档测试(doctests)
文档字符串中的示例代码也需要验证。例如WhisperModel.forward的 docstring 中带有>>>形式的示例(加载模型、特征提取器、构造输入并输出[1, 2, 512]形状的last_hidden_state)。自动测试指定文件内所有 docstring 示例:
pytest --doctest-modules <path_to_file_or_dir>若目标是 Markdown 扩展名的文件,需要追加:
pytest --doctest-glob="*.md"仓库根目录的 doctest_list.txt 及 utils/check_doctest_list.py 还进一步维护了文档测试清单与一致性检查逻辑,可配合make check-repository-consistency使用。
只运行与修改相关的测试
使用 [pytest-picked] 插件,可只运行尚未提交(Git 未暂存)或当前分支相关的文件所对应的测试,快速确认改动没有破坏任何东西:
pip install pytest-picked pytest --picked源码修改时自动重跑失败测试
[pytest-xdist] 的-f/--looponfail模式会在你修改文件期间持续重跑失败测试,直到全部通过后再做一次完整运行:
pip install pytest-xdist pytest -f # 等价于 pytest --looponfail文件变更检测以looponfailroots根目录及其递归内容为范围,默认值不适用时可在setup.cfg、pytest.ini或tox.ini中覆盖:
[tool:pytest] looponfailroots = transformers tests或:
[pytest] looponfailroots = transformers tests注意:文件变更检测只会在 ini 文件所在目录下指定的目录中寻找。pytest-watch是该功能的替代实现。
排除某些测试模块
想运行除特定模式外的所有测试时,可显式构造文件列表。例如排除所有test_modeling_*测试:
pytest $(ls -1 tests/*.py | grep -v test_modeling*)清理缓存
CI 构建对缓存隔离要求高,可清空 pytest 缓存再运行:
pytest --cache-clear tests并行运行测试
如前所述,make test通过 pytest-xdist 以-n X参数并行执行(如-n 2表示 2 个并行作业)。--dist=选项控制测试如何分组,--dist=loadfile将同一文件中的测试放入同一进程。由于并行会打乱执行顺序,若暴露出未被发现的耦合测试(interdependent tests),可用 [pytest-replay] 按相同顺序重放测试,进而逐步缩小到最小的失败序列。
测试顺序与重复
重复、随机或按集合多次运行测试,有助于发现状态耦合类 bug(如 teardown 不干净)以及深度学习随机性引发的问题。
重复运行测试
[pytest-flakefinder] 可把每个测试重复执行多次(默认 50 次):
pip install pytest-flakefinder pytest --flake-finder --flake-runs=5 tests/test_failing_test.py注意:该插件不能与 pytest-xdist 的
-n标志同时使用。 另一个插件 pytest-repeat 同样可实现重复,但它不支持unittest测试。
随机顺序运行
安装 [pytest-random-order] 后测试会自动随机化,无需额外配置:
pip install pytest-random-order它会打印本次会话使用的随机种子,例如:
pytest tests [...] Using --random-order-bucket=module Using --random-order-seed=573663当某个随机序列失败时,用该种子即可精确复现:
pytest --random-order-seed=573663若你手动收窄了测试列表,则种子不再适用,需按失败时的精确顺序手动列出文件,并配合--random-order-bucket=none关闭随机化:
pytest --random-order-bucket=none tests/test_a.py tests/test_c.py tests/test_b.py完全关闭所有洗牌:
pytest --random-order-bucket=none默认--random-order-bucket=module在模块层面洗牌,也支持class、package、global、none层级。另一个功能相近的插件是 [pytest-randomly],但它没有 bucket 模式,且同样是安装后自动启用。
测试报告外观与反馈
- [pytest-sugar]:美化输出、进度条、即时显示失败断言,安装后自动生效;临时禁用可用
pytest -p no:sugar; - [pytest-pspec]:逐个显示每个子测试名称与进度:
pytest --pspec tests/optimization/test_optimization.py; - [pytest-instafail]:失败与错误即时呈现,无需等待会话结束:
pytest --instafail。
测试环境控制:GPU、后端与输出
GPU 选择与@require_*装饰器
在带 GPU 的环境中强制以纯 CPU 模式测试:
CUDA_VISIBLE_DEVICES="" pytest tests/utils/test_logging.py多 GPU 时指定使用哪块卡(例如只用第二块 GPU):
CUDA_VISIBLE_DEVICES="1" pytest tests/utils/test_logging.py这适合在不同 GPU 上并行跑不同任务。文档还给出了一组用于声明 CPU/GPU/TPU 需求的 skip 装饰器,全部定义在 src/transformers/testing_utils.py:
| GPU 数量要求 | 装饰器 |
|---|---|
>= 0 | @require_torch |
>= 1 | @require_torch_gpu |
>= 2 | @require_torch_multi_gpu |
< 2 | @require_torch_non_multi_gpu |
< 3 | @require_torch_up_to_2_gpus |
例如,仅当至少 2 块 GPU 且 PyTorch 已安装时才运行的测试:
@require_torch_multi_gpu def test_example_with_multi_gpu(): ...这些装饰器可以叠加,例如"慢速 + 至少 1 块 GPU":
@require_torch_gpu @slow def test_example_slow_on_gpu(): ...注意装饰器顺序:@parameterized.expand(...)之类的装饰器会改写测试名,因此@require_*与@slow必须**放在最下面(最后应用)**才能正确生效;而@pytest.mark.parametrize不存在此顺序问题(但仅对非 unittest 测试有效)。测试内部可直接获取可用 GPU 数量:
from transformers.testing_utils import get_gpu_count n_gpu = get_gpu_count() # torch 与 tf 下均可用从 src/transformers/testing_utils.py 的实现看,get_gpu_count()在 PyTorch 可用时返回torch.cuda.device_count(),否则返回 0。
指定 PyTorch 后端或设备
通过环境变量TRANSFORMERS_TEST_DEVICE在指定设备上运行测试套件:
TRANSFORMERS_TEST_DEVICE="cpu" pytest tests/utils/test_logging.py该变量对mps等自定义或不常见的 PyTorch 后端尤其有用,也可替代CUDA_VISIBLE_DEVICES实现同样的设备约束效果。某些后端在首次import torch后还需要额外的导入,此时用TRANSFORMERS_TEST_BACKEND指定:
TRANSFORMERS_TEST_BACKEND="torch_npu" pytest tests/utils/test_logging.py分布式训练测试
pytest 本身不能直接处理分布式训练——若直接尝试,子进程会误以为自己也是 pytest 而循环跑整个测试套件。正确做法是:由测试进程派生普通进程,再由它派生多个 worker 并管理 IO 管道。仓库中的代表实现:
- tests/trainer/distributed/test_trainer_distributed.py
- 以及 deepspeed 相关的分布式测试
在这些测试中搜索execute_subprocess_async调用即可直达执行点。运行分布式测试至少需要 2 块 GPU:
CUDA_VISIBLE_DEVICES=0,1 RUN_SLOW=1 pytest -sv tests/trainer/distributed/test_trainer_distributed.py输出捕获
测试运行期间发往stdout/stderr的输出会被捕获;测试或 setup 方法失败时,对应捕获内容会随失败回溯一起展示。禁用捕获、让输出正常透传:
pytest -s tests/utils/test_logging.py # 等价写法 pytest --capture=no tests/utils/test_logging.py输出 JUnit 格式结果:
py.test tests --junitxml=result.xml颜色控制
关闭彩色输出(例如黄字配白底难以阅读时):
pytest --color=no tests/utils/test_logging.py发送测试报告到在线 pastebin
为每个失败生成 URL:
pytest --pastebin=failed tests/utils/test_logging.py这会将会话信息发送到远程 Paste 服务并为每个错误返回 URL,可配合-x只发送首个失败。为整个会话日志生成 URL:
pytest --pastebin=all tests/utils/test_logging.py编写测试
unittest 与 pytest 的混合使用
🤗 Transformers 的测试基于unittest,但由pytest驱动,因此两种体系的特性大多可用。关键限制是:大多数 pytest fixture 不适用;参数化也一样,需改用行为类似的parameterized模块(在 unittest 中)。
参数化:parameterized 与 pytest.mark.parametrize
有时需要以不同参数多次运行同一测试。若在测试内部写循环,则无法单独运行某一个参数组合,因此应使用参数化:
# test_this1.py import unittest from parameterized import parameterized class TestMathUnitTest(unittest.TestCase): @parameterized.expand( [ ("negative", -1.5, -2.0), ("integer", 1, 1.0), ("large fraction", 1.6, 1), ] ) def test_floor(self, name, input, expected): assert_equal(math.floor(input), expected)默认该测试会执行 3 次,test_floor的最后三个参数依次取参数列表中的对应值。可按参数名筛选子集:
# 只跑 negative 与 integer 两组 pytest -k "negative and integer" tests/test_mytest.py # 排除 negative 子测试 pytest -k "not negative" tests/test_mytest.py除-k外,也可以先收集出每个子测试的精确名称:
pytest test_this1.py --collect-only -q输出类似:
test_this1.py::TestMathUnitTest::test_floor_0_negative test_this1.py::TestMathUnitTest::test_floor_1_integer test_this1.py::TestMathUnitTest::test_floor_2_large_fraction从而只运行两个特定子测试:
pytest test_this1.py::TestMathUnitTest::test_floor_0_negative test_this1.py::TestMathUnitTest::test_floor_1_integerparameterized已包含在 transformers 的开发依赖中,unittest与pytest测试均可用。若测试不是 unittest,则可以使用pytest.mark.parametrize(现有测试中主要见于examples目录):
# test_this2.py import pytest @pytest.mark.parametrize( "name, input, expected", [ ("negative", -1.5, -2.0), ("integer", 1, 1.0), ("large fraction", 1.6, 1), ], ) def test_floor(name, input, expected): assert_equal(math.floor(input), expected)其收集出的子测试命名略有不同:
test_this2.py::test_floor[integer-1-1.0] test_this2.py::test_floor[negative--1.5--2.0] test_this2.py::test_floor[large fraction-1.6-1]同样可以精确运行:
pytest "test_this2.py::test_floor[negative--1.5--2.0]" "test_this2.py::test_floor[integer-1-1.0]"文件与目录路径:TestCasePlus
测试中常需要获知相对当前测试文件的位置,但测试可能从多个目录被调用、或位于不同深度的子目录中,直接计算很麻烦。transformers.testing_utils.TestCasePlus帮助类统一整理并提供全部基础路径访问器,其实现见 src/transformers/testing_utils.py(setUp中通过向上逐级查找同时包含src与tests的目录来确定仓库根)。
pathlib对象(均为完整解析路径):test_file_path—— 当前测试文件路径(即__file__)test_file_dir—— 当前测试文件所在目录tests_dir——tests套件目录examples_dir——examples套件目录repo_root_dir—— 仓库根目录src_dir——transformers子包所在目录(即src)
- 字符串版本(同上,但返回
str而非pathlib.Path):test_file_path_str、test_file_dir_str、tests_dir_str、examples_dir_str、repo_root_dir_str、src_dir_str。
用法示例——只需让测试类继承TestCasePlus:
from transformers.testing_utils import TestCasePlus class PathExampleTest(TestCasePlus): def test_something_involving_local_locations(self): data_dir = self.tests_dir / "fixtures/tests_samples/wmt_en_ro"需要字符串时直接调用str()或使用_str后缀访问器:
from transformers.testing_utils import TestCasePlus class PathExampleTest(TestCasePlus): def test_something_involving_stringified_locations(self): examples_dir = self.examples_dir_str临时文件与目录
并行测试中,使用唯一临时文件/目录必不可少,既防止测试间互相覆盖数据,又要求在测试结束时自动清理。tempfile等标准库可以满足基本需求,但调试时你往往需要知道临时目录里到底有什么,并希望路径在每次重跑时固定不变——这正是get_auto_remove_tmp_dir的价值所在。
创建唯一临时目录(测试结束自动删除):
from transformers.testing_utils import TestCasePlus class ExamplesTests(TestCasePlus): def test_whatever(self): tmp_dir = self.get_auto_remove_tmp_dir()创建自定义临时目录,并在测试开始前确保其为空、测试结束后保留内容:
def test_whatever(self): tmp_dir = self.get_auto_remove_tmp_dir("./xxx")这适合调试时监控特定目录,并确认前序测试没有在其中留下数据。通过before与after参数可覆盖默认行为:
before=True:测试开始时总是清空临时目录;before=False:目录已存在时保留其中已有文件;after=True:测试结束时总是删除临时目录;after=False:测试结束时总是保留临时目录。
实现层面(见 src/transformers/testing_utils.py):传tmp_dir=None时默认before=True、after=True;传显式tmp_dir时默认before=True、after=False(更符合调试预期)。
安全提示:为安全执行
rm -r的等价操作,当使用显式tmp_dir时,只允许传入项目仓库检出目录的子目录,防止误删/tmp等文件系统重要位置,务必始终使用./开头的路径。 每个测试可注册多个临时目录,除非另行要求,否则全部自动删除。
临时 sys.path 覆盖
需要临时扩展sys.path以从其他测试目录导入时,使用ExtendSysPath上下文管理器:
import os from transformers.testing_utils import ExtendSysPath bindir = os.path.abspath(os.path.dirname(__file__)) with ExtendSysPath(f"{bindir}/.."): from test_trainer import TrainerIntegrationCommon # noqa跳过测试:skip 与 xfail
当发现 bug 并写了新测试、但 bug 尚未修复时,需要让该测试在make test中跳过以便合入主仓库。两种机制的区别:skip不执行测试,xfail会执行并预期其失败——因此若 bug 代码会影响其他测试,请勿使用xfail。
实现方式:
无条件跳过整个测试(unittest 写法):
@unittest.skip(reason="this bug needs to be fixed") def test_feature_x():pytest 写法:
@pytest.mark.skip(reason="this bug needs to be fixed")或预期失败:
@pytest.mark.xfail def test_feature_x():基于测试内部检查跳过:
def test_feature_x(): if not has_something(): pytest.skip("unsupported configuration")跳过整个模块:
import pytest if not pytest.config.getoption("--custom-flag"): pytest.skip("--custom-flag is missing, skipping tests", allow_module_level=True)在测试内部标记 xfail:
def test_feature_x(): pytest.xfail("expected to fail until bug XYZ is fixed")因缺失导入而跳过模块内全部测试:
docutils = pytest.importorskip("docutils", minversion="0.3")按条件跳过:
@pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python3.6 or higher") def test_feature_x():@unittest.skipIf(torch_device == "cpu", "Can't do half precision") def test_feature_x():按平台跳过整个类:
@pytest.mark.skipif(sys.platform == 'win32', reason="does not run on windows") class TestClass(): def test_feature_x(self):slow 测试
测试库不断膨胀,部分测试耗时数分钟,CI 不可能每次都等一小时。因此除少数例外,慢测试都应标记@slow:
from transformers.testing_utils import slow @slow def test_integration_foo():@slow装饰器由 src/transformers/testing_utils.py 定义,默认跳过慢测试,只有设置RUN_SLOW环境变量为真值才运行(源码中通过parse_flag_from_env("RUN_SLOW", default=False)解析):
RUN_SLOW=1 pytest tests与@require_*同理,@slow也必须放在@parameterized.expand(...)等改写测试名的装饰器之后(最下面):
@parameterized.expand(...) @slow def test_integration_foo():哪些测试应标记为 slow?文档给出的决策机制如下:
- 聚焦库内部组件的测试(模型文件、分词器文件、Pipeline)应放入慢测试套件;聚焦文档或示例等库其他方面的测试同理,并可按此原则细化例外;
- 需要下载大权重(约 50MB 以上)或大数据集的所有测试(模型/分词器/Pipeline 集成测试)都应标记 slow。新增模型时,应为集成测试创建带随机权重的小型版本并上传到 hub;
- 所有需要训练(且未特别加速)的测试都应标记 slow;
- 可引入例外:某些本不该慢的测试实际很慢时可标记
@slow,例如保存/加载大文件到磁盘的自动建模测试; - CI 中 1 秒内能完成(含下载)的测试应作为常规测试。
常规(非 slow)测试需要快速覆盖各种内部要素,通常借助特意构造的 tiny 模型(最小层数、小词表等)实现高覆盖率,而@slow测试则用大型真实模型做定性验证。在仓库中搜索 tiny 模型的使用:
grep tiny tests examples脚本目录 scripts 中提供了可调整生成 tiny 模型的示例脚本(如基于 fsmt 制作 tiny-wmt19-en-de 风格小模型的脚本),可按需适配特定模型架构。
最后注意:运行时间容易被误测——大模型下载的开销在本地会因缓存而不计入,因此务必查看 CI 日志中的运行速度报告(pytest --durations=0 tests的输出)。该报告还能帮助发现未标记 slow 的慢速离群测试,以及需要重写提速的测试;套件变慢时,报告顶部就是最慢的测试列表。
测试 stdout/stderr 输出
使用 pytest 的capsys机制访问捕获的输出流:
import sys def print_to_stdout(s): print(s) def print_to_stderr(s): sys.stderr.write(s) def test_result_and_stdout(capsys): msg = "Hello" print_to_stdout(msg) print_to_stderr(msg) out, err = capsys.readouterr() # 消费捕获的输出流 # 可选:回放已消费的流 sys.stdout.write(out) sys.stderr.write(err) # 断言: assert msg in out assert msg in errstderr 经常作为异常的一部分出现,此时用 try/except 断言:
def raise_exception(msg): raise ValueError(msg) def test_something_exception(): msg = "Not a good value" error = "" try: raise_exception(msg) except Exception as e: error = str(e) assert msg in error, f"{msg} is in the exception:\n{error}"另一种捕获 stdout 的方式是contextlib.redirect_stdout:
from io import StringIO from contextlib import redirect_stdout def print_to_stdout(s): print(s) def test_result_and_stdout(): msg = "Hello" buffer = StringIO() with redirect_stdout(buffer): print_to_stdout(msg) out = buffer.getvalue() # 可选:回放消费的流 sys.stdout.write(out) # 断言: assert msg in out捕获 stdout 时有个坑:普通print可能输出包含\r的内容(如进度条),会重置此前已输出的文本。pytest 本身没问题,但pytest -s下这些字符会留在缓冲区里。为保证加不加-s结果一致,需要清理:re.sub(r'~.*\r', '', buf, 0, re.M)。仓库为此提供了自动处理这一切的封装上下文管理器CaptureStdout(实现见 src/transformers/testing_utils.py,内部用apply_print_resets即re.sub(r"^.*\r", "", buf, 0, re.MULTILINE)清洗输出):
from transformers.testing_utils import CaptureStdout with CaptureStdout() as cs: function_that_writes_to_stdout() print(cs.out)完整示例:
from transformers.testing_utils import CaptureStdout msg = "Secret message\r" final = "Hello World" with CaptureStdout() as cs: print(msg + final) assert cs.out == final + "\n", f"captured: {cs.out}, expecting {final}"捕获 stderr 用CaptureStderr,同时捕获两个流用父类CaptureStd:
from transformers.testing_utils import CaptureStderr with CaptureStderr() as cs: function_that_writes_to_stderr() print(cs.err)from transformers.testing_utils import CaptureStd with CaptureStd() as cs: function_that_writes_to_stdout_and_stderr() print(cs.err, cs.out)默认情况下,这些上下文管理器在退出时会自动把捕获的流回放到原上下文,便于调试测试问题(可通过replay=False关闭)。
捕获 logger 流
需要验证 logger 输出时使用CaptureLogger(用法与上述 Capture 系列一致,实现见 src/transformers/testing_utils.py):
from transformers import logging from transformers.testing_utils import CaptureLogger msg = "Testing 1, 2, 3" logging.set_verbosity_info() logger = logging.get_logger("transformers.models.bart.tokenization_bart") with CaptureLogger(logger) as cl: logger.info(msg) assert cl.out, msg + "\n"环境变量测试
用transformers.testing_utils.mockenv装饰器在指定测试中模拟环境变量:
from transformers.testing_utils import mockenv class HfArgumentParserTest(unittest.TestCase): @mockenv(TRANSFORMERS_VERBOSITY="error") def test_env_override(self): env_level_str = os.getenv("TRANSFORMERS_VERBOSITY", None)(tests/utils/test_logging.py中的HfArgumentParserTest.test_env_override正是这一模式的真实用例。)当测试需要调用外部程序、必须在os.environ中设置PYTHONPATH以包含多个本地路径时,使用TestCasePlus.get_env():
from transformers.testing_utils import TestCasePlus class EnvExampleTest(TestCasePlus): def test_external_prog(self): env = self.get_env() # 现在把 env 传给外部程序get_env()(src/transformers/testing_utils.py)会根据测试文件位于tests还是examples套件,在PYTHONPATH中分别加入对应目录,同时始终加入src目录以确保测试针对当前仓库的代码运行,最后保留预设的PYTHONPATH。该方法返回os.environ的副本,原始对象不受影响。
获得可复现结果
某些场景需要消除随机性,可通过固定各 RNG 的种子实现:
seed = 42 # python RNG import random random.seed(seed) # pytorch RNGs import torch torch.manual_seed(seed) torch.backends.cudnn.deterministic = True if torch.cuda.is_available(): torch.cuda.manual_seed_all(seed) # numpy RNG import numpy as np np.random.seed(seed)调试测试
在警告发生的位置启动调试器:
pytest tests/utils/test_logging.py -W error::UserWarning --pdb与 GitHub Actions 工作流协作
要触发 self-push 工作流 CI 作业,按以下步骤操作:
- 在
transformers的远程仓库(注意:是原仓库而非 fork)中新建分支; - 分支名必须以
ci_或ci-开头(main也会触发,但main无法开 PR);且只在特定路径下触发,最新定义以.github/workflows/self-push.yml的push:字段为准; - 从该分支创建 PR;
- 作业随后会出现在 self-push 工作流运行列表中;若存在积压,作业可能不会立即执行。
测试实验性 CI 功能
新 CI 功能若直接混入常规流水线,可能干扰正常 CI 的运行。正确做法是:
- 为待测功能创建独立的新作业;
- 新作业必须始终显示绿色 ✓(原因见下文);
- 运行数天,覆盖各种 PR 形态(用户 fork 分支、非 fork 分支、从 github.com UI 直接编辑文件的分支、各种强推等),并监控实验作业的日志(而非整个作业的绿色状态);
- 确认稳定后,再把改动合并进现有作业。
如何让实验中的作业"永远成功"?TravisCI 支持ignore-step-failure将整个作业报告为成功,但 CircleCI 与 GitHub Actions 当前并不支持。可用的变通方案:
- 在 bash 脚本开头用
set +euo pipefail抑制潜在失败; - 确保最后一条命令成功,例如
echo "done"或true。
示例:
- run: name: run CI experiment command: | set +euo pipefail echo "setting run-all-despite-any-errors-mode" this_command_will_fail echo "but bash continues to run" # emulate another failure false # but the last command must be a success echo "during experiment do not remove: reporting success to CI, even if there were failures"简单命令也可写作:
cmd_that_may_fail || true对实验结果满意后,将实验步骤/作业并入常规作业,并删除set +euo pipefail等附加内容,确保实验不再干扰常规 CI 行为。如果 CI 能提供类似allow-failure的机制,整个过程会简单得多,但如前所述,目前 CircleCI 与 GitHub Actions 尚未支持。
结语
从两套测试套件的职责划分,到pytest的筛选、并行、随机化与报告技巧,再到TestCasePlus、CaptureStdout、mockenv、@slow与@require_*等仓库内置测试设施,本文完整覆盖了 🤗 Transformers 官方《Testing》文档的核心内容,并补充了 Makefile、src/transformers/testing_utils.py、tests/utils/test_logging.py 等当前仓库源码中的实现证据。无论是为新增模型编写集成测试、在 PR 前自查改动,还是参与 CI 流程建设,这套方法论都同样适用于其他基于 pytest/unittest 的机器学习项目。
【免费下载链接】transformers🤗 Transformers: the model-definition framework for state-of-the-art machine learning models in text, vision, audio, and multimodal models, for both inference and training.项目地址: https://gitcode.com/GitHub_Trending/tra/transformers
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考