[特殊字符] Transformers 测试指南:从 CI 流水线到测试编写实战
2026/9/10 2:07:07 网站建设 项目流程

🤗 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》文档的深度解读与实战指南,系统梳理了该开源仓库的两套测试套件(testsexamples)、CI 测试触发机制、pytest 全场景运行技巧,以及基于unittest/pytest的测试编写规范。读完本文,你将掌握如何选择与运行特定测试、如何用@slow@require_*等装饰器管理测试等级与硬件依赖、如何利用TestCasePlusCaptureStdout等测试工具类编写健壮的测试用例,并能正确参与仓库的 CI 协作流程。

了解 🤗 Transformers 的测试体系

两套测试套件

本仓库包含两套测试套件,职责划分明确:

  1. tests—— 针对通用 API 的测试,覆盖模型、分词器、Pipeline、Trainer 等核心组件;
  2. 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 且只更新了srctests.github目录时,在 GPU 上运行快速测试;
    • self-hosted runner:在 GPU 上按固定计划运行testsexamples的常规测试和慢测试,等价于:
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.py

tests/utils/test_logging.py是文档中反复出现的示例文件,其中定义了HfArgumentParserTest测试类以及test_set_leveltest_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支持按测试名称的关键字子串过滤,并可组合andornot

# 只运行名称含 "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.cfgpytest.initox.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在模块层面洗牌,也支持classpackageglobalnone层级。另一个功能相近的插件是 [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_integer

parameterized已包含在 transformers 的开发依赖中,unittestpytest测试均可用。若测试不是 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中通过向上逐级查找同时包含srctests的目录来确定仓库根)。

  • 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_strtest_file_dir_strtests_dir_strexamples_dir_strrepo_root_dir_strsrc_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")

这适合调试时监控特定目录,并确认前序测试没有在其中留下数据。通过beforeafter参数可覆盖默认行为:

  • before=True:测试开始时总是清空临时目录;
  • before=False:目录已存在时保留其中已有文件;
  • after=True:测试结束时总是删除临时目录;
  • after=False:测试结束时总是保留临时目录。

实现层面(见 src/transformers/testing_utils.py):传tmp_dir=None时默认before=Trueafter=True;传显式tmp_dir时默认before=Trueafter=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 err

stderr 经常作为异常的一部分出现,此时用 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_resetsre.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 作业,按以下步骤操作:

  1. transformers的远程仓库(注意:是原仓库而非 fork)中新建分支;
  2. 分支名必须以ci_ci-开头(main也会触发,但main无法开 PR);且只在特定路径下触发,最新定义以.github/workflows/self-push.ymlpush:字段为准;
  3. 从该分支创建 PR;
  4. 作业随后会出现在 self-push 工作流运行列表中;若存在积压,作业可能不会立即执行。

测试实验性 CI 功能

新 CI 功能若直接混入常规流水线,可能干扰正常 CI 的运行。正确做法是:

  1. 为待测功能创建独立的新作业;
  2. 新作业必须始终显示绿色 ✓(原因见下文);
  3. 运行数天,覆盖各种 PR 形态(用户 fork 分支、非 fork 分支、从 github.com UI 直接编辑文件的分支、各种强推等),并监控实验作业的日志(而非整个作业的绿色状态);
  4. 确认稳定后,再把改动合并进现有作业。

如何让实验中的作业"永远成功"?TravisCI 支持ignore-step-failure将整个作业报告为成功,但 CircleCI 与 GitHub Actions 当前并不支持。可用的变通方案:

  1. 在 bash 脚本开头用set +euo pipefail抑制潜在失败;
  2. 确保最后一条命令成功,例如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的筛选、并行、随机化与报告技巧,再到TestCasePlusCaptureStdoutmockenv@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),仅供参考

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

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

立即咨询