Transformers 文件

測試

Hugging Face's logo
加入 Hugging Face 社群

並獲得增強的文件體驗

開始使用

測試

讓我們來看看 🤗 Transformers 模型是如何進行測試的,以及如何編寫新的測試和改進現有測試。

倉庫中有 2 個測試套件:

  1. tests — 通用 API 的測試
  2. examples — 主要用於不屬於 API 的各種應用程式的測試

Transformers 如何進行測試

  1. 一旦提交 PR,它將透過 9 個 CircleCi 任務進行測試。該 PR 的每次新提交都會重新測試。這些任務在此配置檔案中定義,因此如果需要,您可以在自己的機器上重現相同的環境。

    這些 CI 任務不執行 @slow 測試。

  2. 有 3 個任務由 github actions 執行

    • torch hub 整合:檢查 torch hub 整合是否有效。

    • 自託管(推送):僅在 main 分支的提交上執行 GPU 上的快速測試。僅當 main 分支上的提交更新了以下資料夾之一中的程式碼時才會執行:srctests.github(以防止在新增的模型卡、筆記本等上執行)

    • 自託管執行器:在 testsexamples 中執行 GPU 上的正常測試和慢速測試

RUN_SLOW=1 pytest tests/
RUN_SLOW=1 pytest examples/

結果可以在這裡檢視。

執行測試

選擇要執行的測試

本文件詳細介紹瞭如何執行測試。如果閱讀完所有內容後,您需要更多詳細資訊,您可以在這裡找到它們。

以下是一些最常用的執行測試的方法。

全部執行

pytest

或者

make test

請注意,後者定義為

python -m pytest -n auto --dist=loadfile -s -v ./tests/

這告訴 pytest:

  • 執行與 CPU 核心數相同的測試程序(如果您沒有大量 RAM,這可能會太多!)
  • 確保同一檔案中的所有測試都將由同一測試程序執行
  • 不捕獲輸出
  • 在詳細模式下執行

獲取所有測試列表

測試套件的所有測試

pytest --collect-only -q

給定測試檔案的所有測試

pytest tests/test_optimization.py --collect-only -q

執行特定的測試模組

要執行單個測試模組

pytest tests/utils/test_logging.py

執行特定測試

由於大多數測試中都使用了 unittest,因此要執行特定的子測試,您需要知道包含這些測試的 unittest 類的名稱。例如,它可以是

pytest tests/test_optimization.py::OptimizationTest::test_adam_w

這裡

  • tests/test_optimization.py - 包含測試的檔案
  • OptimizationTest - 類的名稱
  • test_adam_w - 特定測試函式的名稱

如果檔案包含多個類,您可以選擇僅執行給定類的測試。例如

pytest tests/test_optimization.py::OptimizationTest

將執行該類中的所有測試。

如前所述,您可以透過執行以下命令檢視 OptimizationTest 類中包含哪些測試:

pytest tests/test_optimization.py::OptimizationTest --collect-only -q

您可以透過關鍵字表達式執行測試。

要僅執行名稱中包含 adam 的測試:

pytest -k adam tests/test_optimization.py

邏輯 andor 可用於指示所有關鍵字是否應匹配或其中一個。not 可用於否定。

要執行所有測試,但名稱中包含 adam 的測試除外:

pytest -k "not adam" tests/test_optimization.py

您可以將這兩個模式組合在一起

pytest -k "ada and not adam" tests/test_optimization.py

例如,要同時執行 test_adafactortest_adam_w,您可以使用

pytest -k "test_adafactor or test_adam_w" tests/test_optimization.py

請注意,這裡我們使用 or,因為我們希望任一關鍵字匹配以同時包含兩者。

如果您只想包含同時包含兩個模式的測試,則應使用 and

pytest -k "test and ada" tests/test_optimization.py

執行 Accelerate 測試

有時您需要在模型上執行 accelerate 測試。為此,您只需在命令中新增 -m accelerate_tests,例如,如果您想在 OPT 上執行這些測試,請執行

RUN_SLOW=1 pytest -m accelerate_tests tests/models/opt/test_modeling_opt.py

執行文件測試

為了測試文件示例是否正確,您應該檢查 doctests 是否透過。例如,讓我們使用 WhisperModel.forward 的文件字串

r"""
Returns:

Example:
    ```python
    >>> import torch
    >>> from transformers import WhisperModel, WhisperFeatureExtractor
    >>> from datasets import load_dataset

    >>> model = WhisperModel.from_pretrained("openai/whisper-base")
    >>> feature_extractor = WhisperFeatureExtractor.from_pretrained("openai/whisper-base")
    >>> ds = load_dataset("hf-internal-testing/librispeech_asr_dummy", "clean", split="validation")
    >>> inputs = feature_extractor(ds[0]["audio"]["array"], return_tensors="pt")
    >>> input_features = inputs.input_features
    >>> decoder_input_ids = torch.tensor([[1, 1]]) * model.config.decoder_start_token_id
    >>> last_hidden_state = model(input_features, decoder_input_ids=decoder_input_ids).last_hidden_state
    >>> list(last_hidden_state.shape)
    [1, 2, 512]
    ```"""

只需執行以下行即可自動測試所需檔案中的每個文件字串示例

pytest --doctest-modules <path_to_file_or_dir>

如果檔案具有 Markdown 副檔名,則應新增 --doctest-glob="*.md" 引數。

僅執行修改過的測試

您可以使用 pytest-picked 執行與未暫存檔案或當前分支(根據 Git)相關的測試。這是一種快速測試您的更改是否破壞任何東西的好方法,因為它不會執行與您未觸及的檔案相關的測試。

pip install pytest-picked
pytest --picked

所有測試都將從已修改但尚未提交的檔案和資料夾中執行。

原始檔修改時自動重新執行失敗的測試

pytest-xdist 提供了一個非常有用的功能,即檢測所有失敗的測試,然後等待您修改檔案並持續重新執行這些失敗的測試,直到它們透過,同時您修復它們。這樣您在修復後就不需要重新啟動 pytest。這將重複進行,直到所有測試都透過,之後會再次執行完整執行。

pip install pytest-xdist

要進入該模式:pytest -fpytest --looponfail

檔案更改透過檢視 looponfailroots 根目錄及其所有內容(遞迴)來檢測。如果此值的預設設定不適合您,您可以透過在 setup.cfg 中設定配置選項來更改它。

[tool:pytest]
looponfailroots = transformers tests

pytest.ini/tox.ini 檔案

[pytest]
looponfailroots = transformers tests

這將導致僅在相應目錄中查詢檔案更改,這些目錄相對於 ini 檔案目錄指定。

pytest-watch 是此功能的另一種實現。

跳過測試模組

如果您想執行所有測試模組,除了少數幾個,您可以透過提供要執行的測試的顯式列表來排除它們。例如,要執行除 test_modeling_*.py 測試之外的所有測試:

pytest *ls -1 tests/*py | grep -v test_modeling*

清除狀態

CI 構建以及隔離重要(與速度相比)時,應清除快取

pytest --cache-clear tests

並行執行測試

如前所述,make test 透過 pytest-xdist 外掛(-n X 引數,例如 -n 2 執行 2 個並行作業)並行執行測試。

pytest-xdist--dist= 選項允許控制測試如何分組。--dist=loadfile 將一個檔案中的測試放在同一個程序上。

由於執行測試的順序不同且不可預測,如果使用 pytest-xdist 執行測試套件導致失敗(這意味著我們有一些未檢測到的耦合測試),請使用 pytest-replay 以相同的順序重播測試,這應該有助於將失敗序列減少到最小。

測試順序和重複

重複測試多次,按順序、隨機或按組重複,以檢測任何潛在的相互依賴性和狀態相關錯誤(拆除)。而直接的多次重複只是為了檢測一些深度學習隨機性揭示的問題。

重複測試

pip install pytest-flakefinder

然後多次執行每個測試(預設為 50 次)

pytest --flake-finder --flake-runs=5 tests/test_failing_test.py

此外掛不適用於 pytest-xdist-n 標誌。

還有另一個外掛 pytest-repeat,但它不適用於 unittest

隨機順序執行測試

pip install pytest-random-order

重要提示:pytest-random-order 的存在將自動隨機化測試,無需配置更改或命令列選項。

如前所述,這允許檢測耦合測試 - 其中一個測試的狀態會影響另一個測試的狀態。安裝 pytest-random-order 後,它將列印該會話使用的隨機種子,例如

pytest tests
[...]
Using --random-order-bucket=module
Using --random-order-seed=573663

這樣,如果給定特定序列失敗,您可以透過新增該精確種子來重現它,例如:

pytest --random-order-seed=573663
[...]
Using --random-order-bucket=module
Using --random-order-seed=573663

它只有在您使用完全相同的測試列表(或根本不使用列表)時才能重現精確順序。一旦您開始手動縮小列表範圍,您就不能再依賴種子,而必須手動按失敗的精確順序列出它們,並告訴 pytest 不要隨機化它們,而是使用 --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。此模組具有非常相似的功能/介面,但它沒有 pytest-random-order 中可用的桶模式。它存在一旦安裝就會強制執行的相同問題。

外觀變體

pytest-sugar

pytest-sugar 是一個外掛,可改善外觀,新增進度條,並即時顯示失敗的測試和斷言。安裝後它會自動啟用。

pip install pytest-sugar

要不使用它執行測試,請執行

pytest -p no:sugar

或解除安裝它。

報告每個子測試的名稱和進度

對於透過 pytest 執行的單個或一組測試(在 pip install pytest-pspec 之後)

pytest --pspec tests/test_optimization.py

即時顯示失敗的測試

pytest-instafail 可即時顯示失敗和錯誤,而無需等待測試會話結束。

pip install pytest-instafail
pytest --instafail

用 GPU 還是不用 GPU

在啟用 GPU 的設定中,要在僅限 CPU 的模式下測試,請為 CUDA GPU 新增 CUDA_VISIBLE_DEVICES=""

CUDA_VISIBLE_DEVICES="" pytest tests/utils/test_logging.py

或者,如果您有多個 GPU,您可以指定 pytest 使用哪個 GPU。例如,如果您有 GPU 01,要僅使用第二個 GPU,您可以執行

CUDA_VISIBLE_DEVICES="1" pytest tests/utils/test_logging.py

對於 Intel GPU,請在上述示例中使用 ZE_AFFINITY_MASK 而不是 CUDA_VISIBLE_DEVICES

當您想在不同的 GPU 上執行不同的任務時,這很方便。

某些測試必須僅在 CPU 上執行,其他測試可以在 CPU、GPU 或 TPU 上執行,還有一些測試可以在多個 GPU 上執行。以下跳過裝飾器用於設定 CPU/GPU/XPU/TPU 測試的要求:

  • require_torch - 此測試僅在 torch 下執行
  • require_torch_gpu - 同 require_torch,但需要至少 1 個 GPU
  • require_torch_multi_gpu - 同 require_torch,但需要至少 2 個 GPU
  • require_torch_non_multi_gpu - 同 require_torch,但需要 0 或 1 個 GPU
  • require_torch_up_to_2_gpus - 同 require_torch,但需要 0 或 1 或 2 個 GPU
  • require_torch_xla - 同 require_torch,但需要至少 1 個 TPU

讓我們在下表中描述 GPU 要求

N個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():

這些裝飾器可以堆疊。例如,如果一個測試很慢並且在 pytorch 下需要至少一個 GPU,這裡是如何設定它:

@require_torch_gpu
@slow
def test_example_slow_on_gpu():

某些裝飾器(如 @parametrized)會重寫測試名稱,因此 @require_* 跳過裝飾器必須最後列出,才能正常工作。以下是正確用法的示例:

@parameterized.expand(...)
@require_torch_multi_gpu
def test_integration_foo():

這種順序問題在 @pytest.mark.parametrize 中不存在,您可以將其放在第一位或最後一位,它仍然有效。但它只適用於非單元測試。

在測試內部

  • 有多少個 GPU 可用
from transformers.testing_utils import get_gpu_count

n_gpu = get_gpu_count()  # works with torch and tf

使用特定 PyTorch 後端或裝置進行測試

要在特定 torch 裝置上執行測試套件,請新增 TRANSFORMERS_TEST_DEVICE="$device",其中 $device 是目標後端。例如,要僅在 CPU 上進行測試:

TRANSFORMERS_TEST_DEVICE="cpu" pytest tests/utils/test_logging.py

此變數對於測試自定義或不常見的 PyTorch 後端(例如 mpsxpunpu)很有用。它還可以透過指定特定 GPU 或在僅限 CPU 模式下進行測試來達到與 CUDA_VISIBLE_DEVICES 相同的效果。

某些裝置在首次匯入 torch 後需要額外的匯入。這可以透過環境變數 TRANSFORMERS_TEST_BACKEND 指定。

TRANSFORMERS_TEST_BACKEND="torch_npu" pytest tests/utils/test_logging.py

替代後端可能還需要替換裝置特定功能。例如,torch.cuda.manual_seed 可能需要替換為裝置特定種子設定器,如 torch.npu.manual_seedtorch.xpu.manual_seed,以便在裝置上正確設定隨機種子。要在執行測試套件時指定具有後端特定裝置功能的新後端,請建立 Python 裝置規範檔案 spec.py,其格式如下:

import torch
import torch_npu # for xpu, replace it with `import intel_extension_for_pytorch`
# !! Further additional imports can be added here !!

# Specify the device name (eg. 'cuda', 'cpu', 'npu', 'xpu', 'mps')
DEVICE_NAME = 'npu'

# Specify device-specific backends to dispatch to.
# If not specified, will fallback to 'default' in 'testing_utils.py`
MANUAL_SEED_FN = torch.npu.manual_seed
EMPTY_CACHE_FN = torch.npu.empty_cache
DEVICE_COUNT_FN = torch.npu.device_count

此格式還允許指定所需的任何額外匯入。要使用此檔案替換測試套件中的等效方法,請將環境變數 TRANSFORMERS_TEST_DEVICE_SPEC 設定為規範檔案的路徑,例如 TRANSFORMERS_TEST_DEVICE_SPEC=spec.py

目前,僅支援 MANUAL_SEED_FNEMPTY_CACHE_FNDEVICE_COUNT_FN 進行裝置特定分發。

分散式訓練

pytest 無法直接處理分散式訓練。如果嘗試這樣做,子程序將無法正確執行,並最終認為它們是 pytest 並開始迴圈執行測試套件。但是,如果一個程序生成多個 worker 並管理 IO 管道,那麼它就可以正常工作。

以下是一些使用它的測試:

要直接跳到執行點,請在這些測試中搜索 execute_subprocess_async 呼叫。

您需要至少 2 個 GPU 才能看到這些測試的實際效果。

CUDA_VISIBLE_DEVICES=0,1 RUN_SLOW=1 pytest -sv tests/test_trainer_distributed.py

輸出捕獲

在測試執行期間,任何傳送到 stdoutstderr 的輸出都將被捕獲。如果測試或設定方法失敗,其相應的捕獲輸出通常會與失敗回溯一起顯示。

要停用輸出捕獲並正常獲取 stdoutstderr,請使用 -s--capture=no

pytest -s tests/utils/test_logging.py

將測試結果傳送到 JUnit 格式輸出

pytest 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

編寫測試

🤗 transformers 測試基於 unittest,但由 pytest 執行,因此大多數情況下可以使用兩個系統的功能。

您可以在此處閱讀支援的功能,但需要記住的重要一點是,大多數 pytest fixture 都不起作用。引數化也不行,但我們使用以類似方式工作的模組 parameterized

引數化

通常,需要多次執行相同的測試,但使用不同的引數。這可以在測試內部完成,但這樣就無法僅針對一組引數執行該測試。

# 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 的最後 3 個引數分配給引數列表中相應的引數。

你可以只執行 negativeinteger 引數集,使用:

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

現在您只執行 2 個特定子測試:

pytest test_this1.py::TestMathUnitTest::test_floor_0_negative  test_this1.py::TestMathUnitTest::test_floor_1_integer

模組 parameterized 已在 transformers 的開發依賴項中,適用於 unittestspytest 測試。

但是,如果測試不是 unittest,則可以使用 pytest.mark.parametrize(或者您可能會在某些現有測試中看到它被使用,主要是在 examples 下)。

這裡是相同的例子,這次使用 pytestparametrize 標記

# 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)

parameterized 一樣,使用 pytest.mark.parametrize 可以對要執行的子測試進行精細控制,如果 -k 過濾器不起作用。但是,此引數化函式為子測試建立了一組略有不同的名稱。它們看起來像這樣:

pytest test_this2.py --collect-only -q

它將列出

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]

如前一個例子。

檔案和目錄

在測試中,我們經常需要知道檔案相對於當前測試檔案的位置,這並非易事,因為測試可能從多個目錄呼叫,或者可能位於不同深度的子目錄中。輔助類 transformers.test_utils.TestCasePlus 透過整理所有基本路徑並提供對它們的輕鬆訪問器來解決此問題。

  • pathlib 物件(全部完全解析)

    • test_file_path - 當前測試檔案路徑,即 __file__
    • test_file_dir - 包含當前測試檔案的目錄
    • tests_dir - tests 測試套件的目錄
    • examples_dir - examples 測試套件的目錄
    • repo_root_dir - 倉庫的根目錄
    • src_dir - src 目錄(即 transformers 子目錄所在的位置)
  • 字串化路徑——與上述相同,但這些返回路徑為字串,而不是 pathlib 物件

    • test_file_path_str
    • test_file_dir_str
    • tests_dir_str
    • examples_dir_str
    • repo_root_dir_str
    • src_dir_str

要開始使用這些,您只需確保測試位於 transformers.test_utils.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"

如果您不需要透過 pathlib 操作路徑,或者只需要一個字串形式的路徑,則可以始終對 pathlib 物件呼叫 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 等包來滿足這些需求至關重要。

然而,在除錯測試時,您需要能夠檢視臨時檔案或目錄中的內容,並且您想知道它的確切路徑,而不是在每次測試重新執行時都將其隨機化。

輔助類 transformers.test_utils.TestCasePlus 最適合用於此目的。它是 unittest.TestCase 的子類,因此我們可以在測試模組中輕鬆地從它繼承。

以下是其用法示例

from transformers.testing_utils import TestCasePlus


class ExamplesTests(TestCasePlus):
    def test_whatever(self):
        tmp_dir = self.get_auto_remove_tmp_dir()

此程式碼建立一個唯一的臨時目錄,並將 tmp_dir 設定為其位置。

  • 建立唯一的臨時目錄
def test_whatever(self):
    tmp_dir = self.get_auto_remove_tmp_dir()

tmp_dir 將包含建立的臨時目錄的路徑。它將在測試結束時自動刪除。

  • 建立我選擇的臨時目錄,確保它在測試開始前為空,並且在測試後不清空。
def test_whatever(self):
    tmp_dir = self.get_auto_remove_tmp_dir("./xxx")

這對於除錯非常有用,當您想要監視特定目錄並確保之前的測試沒有在那裡留下任何資料時。

  • 您可以透過直接覆蓋 beforeafter 引數來覆蓋預設行為,從而導致以下行為之一:

    • before=True:臨時目錄將在測試開始時始終被清除。
    • before=False:如果臨時目錄已經存在,則任何現有檔案都將保留在那裡。
    • after=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

跳過測試

當發現 bug 並編寫新測試,但 bug 尚未修復時,這很有用。為了能夠將其提交到主倉庫,我們需要確保在 make test 期間跳過它。

方法

  • 跳過 (skip) 意味著您期望您的測試僅在滿足某些條件時才能透過,否則 pytest 應該完全跳過執行測試。常見的例子是在非 Windows 平臺上跳過僅限 Windows 的測試,或者跳過依賴於當前不可用的外部資源(例如資料庫)的測試。

  • xfail (xfail) 意味著您希望測試因某種原因失敗。一個常見的例子是針對尚未實現的功能或尚未修復的 bug 的測試。當測試透過但預期會失敗(標記為 pytest.mark.xfail)時,它是一個 xpass,並將在測試摘要中報告。

兩者之間的一個重要區別是 skip 不執行測試,而 xfail 會執行。因此,如果有問題的程式碼導致一些不良狀態會影響其他測試,請不要使用 xfail

實現

  • 以下是如何無條件跳過整個測試
@unittest.skip(reason="this bug needs to be fixed")
def test_feature_x():

或透過 pytest

@pytest.mark.skip(reason="this bug needs to be fixed")

xfail 方式

@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):

更多詳細資訊、示例和方法請參見此處

慢速測試

測試庫不斷增長,一些測試需要幾分鐘才能執行,因此我們無法在 CI 上等待一個小時才能完成測試套件。因此,除了基本測試的一些例外,慢速測試應按以下示例進行標記:

from transformers.testing_utils import slow
@slow
def test_integration_foo():

一旦測試被標記為 @slow,要執行此類測試,請設定 RUN_SLOW=1 環境變數,例如:

RUN_SLOW=1 pytest tests

某些裝飾器(如 @parameterized)會重寫測試名稱,因此 @slow 和其餘跳過裝飾器 @require_* 必須最後列出,才能正常工作。以下是正確用法的示例:

@parameterized.expand(...)
@slow
def test_integration_foo():

正如本文件開頭所解釋的,慢速測試會按計劃執行,而不是在 PR CI 檢查中執行。因此,PR 提交期間可能會遺漏一些問題並被合併。此類問題將在下次計劃的 CI 任務中被捕獲。但這也意味著在提交 PR 之前在您的機器上執行慢速測試很重要。

以下是選擇哪些測試應標記為慢速的粗略決策機制:

如果測試專注於庫的內部元件之一(例如,建模檔案、分詞檔案、管道),那麼我們應該在非慢速測試套件中執行該測試。如果它專注於庫的其他方面,例如文件或示例,那麼我們應該在慢速測試套件中執行這些測試。然後,為了完善這種方法,我們應該有一些例外:

  • 所有需要下載大量權重或大於約 50MB 的資料集的測試(例如,模型或分詞器整合測試、管道整合測試)都應設定為慢速。如果您要新增新模型,則應建立並上傳到 hub 它的微小版本(帶隨機權重)用於整合測試。這將在以下段落中討論。
  • 所有需要進行未特別最佳化以實現快速訓練的測試都應設定為慢速。
  • 如果其中一些“本應是非慢速”的測試異常慢,我們可以引入例外,並將其設定為 @slow。自動建模測試就是很好的例子,它們會將大檔案儲存和載入到磁碟,因此被標記為 @slow
  • 如果一個測試在 CI 上在 1 秒內完成(包括下載,如果有的話),那麼它應該是一個正常的測試。

總的來說,所有非慢速測試都需要完全覆蓋不同的內部元件,同時保持快速。例如,可以透過使用特製的帶有隨機權重的微型模型進行測試來實現顯著的覆蓋率。這些模型具有最少的層數(例如 2)、詞彙量大小(例如 1000)等。然後,@slow 測試可以使用大型慢速模型進行定性測試。要檢視這些模型的使用,只需查詢帶有以下內容的微型模型:

grep tiny tests examples

以下是一個指令碼示例,它建立了微型模型 stas/tiny-wmt19-en-de。您可以輕鬆地將其調整為您的特定模型架構。

如果存在下載大型模型的開銷,則很容易錯誤地測量執行時,但如果您在本地測試,則下載的檔案將被快取,因此不會測量下載時間。因此,請檢視 CI 日誌中的執行速度報告(pytest --durations=0 tests 的輸出)。

該報告還有助於查詢未標記為慢速或需要重寫以提高速度的慢速異常值。如果您注意到測試套件在 CI 上開始變慢,此報告的頂部列表將顯示最慢的測試。

測試 stdout/stderr 輸出

為了測試寫入 stdout 和/或 stderr 的函式,測試可以使用 pytestcapsys 系統訪問這些流。以下是如何實現:

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()  # consume the captured output streams
    # optional: if you want to replay the consumed streams:
    sys.stdout.write(out)
    sys.stderr.write(err)
    # test:
    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()
    # optional: if you want to replay the consumed streams:
    sys.stdout.write(out)
    # test:
    assert msg in out

捕獲 stdout 的一個重要潛在問題是它可能包含 \r 字元,這些字元在正常 print 中會重置到目前為止已列印的所有內容。pytest 沒有問題,但是使用 pytest -s 這些字元會被包含在緩衝區中,因此為了使測試能夠在有和沒有 -s 的情況下執行,您必須對捕獲的輸出進行額外的清理,使用 re.sub(r'~.*\r', '', buf, 0, re.M)

但是,我們有一個輔助上下文管理器包裝器,可以自動處理所有這些問題,無論它是否包含一些 \r,所以它很簡單

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

from transformers.testing_utils import CaptureStderr

with CaptureStderr() as cs:
    function_that_writes_to_stderr()
print(cs.err)

如果您需要同時捕獲兩個流,請使用父 CaptureStd

from transformers.testing_utils import CaptureStd

with CaptureStd() as cs:
    function_that_writes_to_stdout_and_stderr()
print(cs.err, cs.out)

此外,為了幫助除錯測試問題,預設情況下,這些上下文管理器會在退出上下文時自動重播捕獲的流。

捕獲日誌流

如果需要驗證日誌記錄器的輸出,可以使用 CaptureLogger

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)

有時需要呼叫外部程式,這需要在 os.environ 中設定 PYTHONPATH 以包含多個本地路徑。輔助類 transformers.test_utils.TestCasePlus 可提供幫助

from transformers.testing_utils import TestCasePlus


class EnvExampleTest(TestCasePlus):
    def test_external_prog(self):
        env = self.get_env()
        # now call the external program, passing `env` to it

根據測試檔案是在 tests 測試套件下還是在 examples 下,它會正確設定 env[PYTHONPATH] 以包含這兩個目錄中的一個,以及 src 目錄,以確保測試是針對當前倉庫進行的,最後還會包含測試呼叫前已設定的 env[PYTHONPATH](如果存在)。

此輔助方法會建立 os.environ 物件的副本,因此原始物件保持不變。

獲得可復現的結果

在某些情況下,您可能希望消除測試的隨機性。要獲得相同的可復現結果集,您需要固定隨機種子。

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 工作流

要觸發自推工作流 CI 作業,您必須

  1. transformers 原始倉庫(而非派生倉庫!)中建立一個新分支。
  2. 分支名稱必須以 ci_ci- 開頭(main 也會觸發,但我們不能在 main 上建立 PR)。它也只針對特定路徑觸發——您可以此處push: 下找到最新定義,以防自本文件編寫以來有所更改。
  3. 從該分支建立 PR。
  4. 然後您可以在此處看到作業出現。如果存在積壓,它可能不會立即執行。

測試實驗性 CI 功能

測試 CI 功能可能會有問題,因為它可能干擾正常的 CI 功能。因此,如果要新增新的 CI 功能,應按以下方式進行。

  1. 建立一個新的專用作業來測試需要測試的內容
  2. 新作業必須始終成功,以便我們獲得綠色勾號(詳情如下)。
  3. 讓它執行幾天,觀察各種不同型別的 PR 是否能在其上執行(使用者派生分支、非派生分支、源自 github.com UI 直接檔案編輯的分支、各種強制推送等——型別繁多),同時監控實驗性作業的日誌(而非整體作業的綠色狀態,因為它是特意設定為始終綠色的)
  4. 當一切都穩定後,將新更改合併到現有作業中。

這樣,對 CI 功能本身的實驗就不會干擾正常的工作流程。

那麼,在開發新的 CI 功能時,我們如何才能讓作業始終成功呢?

一些 CI(如 TravisCI)支援忽略步驟失敗並會將整體作業報告為成功,但截至本文撰寫時,CircleCI 和 Github Actions 尚不支援此功能。

因此,可以使用以下變通方法

  1. 在執行命令的開頭使用 set +euo pipefail 來抑制 bash 指令碼中大多數潛在的失敗。
  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 功能。

如果我們可以為實驗步驟設定類似 allow-failure 的東西,並允許它失敗而不影響 PR 的整體狀態,那麼整個過程會容易得多。但如前所述,CircleCI 和 Github Actions 目前不支援此功能。

您可以在這些 CI 特定執行緒中投票支援此功能並檢視其進展

DeepSpeed 整合

對於涉及 DeepSpeed 整合的 PR,請注意我們的 CircleCI PR CI 設定沒有 GPU。需要 GPU 的測試在不同的 CI 夜間執行。這意味著如果您的 PR 中通過了 CI 報告,並不意味著 DeepSpeed 測試透過。

要執行 DeepSpeed 測試

RUN_SLOW=1 pytest tests/deepspeed/test_deepspeed.py

對建模或 PyTorch 示例程式碼的任何更改也需要執行模型庫測試。

RUN_SLOW=1 pytest tests/deepspeed
< > 在 GitHub 上更新

© . This site is unofficial and not affiliated with Hugging Face, Inc.