Transformers documentation

Testing

You are viewing v4.44.2 version. A newer version v4.46.3 is available.
Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

Testing

🤗 Transformersモデルがどのようにテストされ、新しいテストを書いて既存のテストを改善できるかを見てみましょう。

このリポジトリには2つのテストスイートがあります:

  1. tests — 一般的なAPI用のテスト
  2. examples — APIの一部ではないさまざまなアプリケーション用のテスト

How transformers are tested

  1. PRが提出されると、9つのCircleCiジョブでテストされます。PRへの新しいコミットごとに再テストされます。これらのジョブは、この設定ファイルで定義されており、必要な場合は同じ環境を自分のマシンで再現できます。

    これらのCIジョブは @slow テストを実行しません。

  2. GitHub Actionsによって実行される3つのジョブがあります:

    • torch hub integration: torch hubの統合が動作するかどうかを確認します。

    • self-hosted (push): main にコミットが行われた場合に、GPUで高速テストを実行します。このジョブは、main でのコミットが以下のフォルダーのコードを更新した場合にのみ実行されます:srctests.github(追加されたモデルカード、ノートブックなどの実行を防ぐため)。

    • self-hosted runner: GPUで testsexamples の通常のテストと遅いテストを実行します。

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

結果はhereで観察できます。

Running tests

Choosing which tests to run

このドキュメントは、テストを実行する方法の多くの詳細について説明しています。すべてを読んだ後でも、さらに詳細が必要な場合は、こちらで見つけることができます。

以下は、テストを実行するためのいくつかの最も便利な方法です。

すべて実行します:

pytest

または:

make test

後者は次のように定義されることに注意してください。

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

以下は、pytestに渡す設定情報です。

  • テストプロセスをCPUコアの数と同じだけ実行するように指示します。ただし、RAMが十分でない場合は注意が必要です。
  • 同じファイルからのすべてのテストは、同じテストプロセスで実行されるようにします。
  • 出力のキャプチャを行いません。
  • 冗長モードで実行します。

Getting the list of all tests

テストスイートのすべてのテスト:

pytest --collect-only -q

指定されたテスト ファイルのすべてのテスト:

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

Run a specific test module

個別のテスト モジュールを実行するには:

pytest tests/utils/test_logging.py

Run specific tests

ほとんどのテストで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

andおよびorは、すべてのキーワードが一致するか、いずれかを示すために使用できます。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_adam_w or test_adam_w" tests/test_optimization.py

注意: ここでは、or を使用しています。キーワードのいずれか一つが一致すれば、両方を含めるためです。

両方のパターンを含むテストのみを含めたい場合は、and を使用してください。

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

Run accelerate tests

時々、モデルに対して accelerate テストを実行する必要があります。たとえば、OPT 実行に対してこれらのテストを実行したい場合、コマンドに -m accelerate_tests を追加するだけで済みます:

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

Run documentation tests

ドキュメンテーションの例が正しいかどうかをテストするには、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>

ファイルにマークダウン拡張子がある場合は、--doctest-glob="*.md"引数を追加する必要があります。

Run only modified tests

pytest-pickedを使用すると、未ステージングのファイルまたは現在のブランチ(Gitに従って)に関連するテストを実行できます。これは、変更内容に関連するテストのみ実行されるため、変更が何も壊れていないことを迅速に確認する素晴らしい方法です。変更されていないファイルに関連するテストは実行されません。

pip install pytest-picked
pytest --picked

すべてのテストは、変更されたがまだコミットされていないファイルとフォルダから実行されます。

Automatically rerun failed tests on source modification

pytest-xdistは、非常に便利な機能を提供しており、すべての失敗したテストを検出し、ファイルを修正する間にそれらの失敗したテストを連続して再実行することができます。そのため、修正を行った後にpytestを再起動する必要がありません。すべてのテストが合格するまで繰り返され、その後再度フルランが実行されます。

pip install pytest-xdist

モードに入るには: pytest -fまたはpytest --looponfail

ファイルの変更は、looponfailrootsルートディレクトリとその内容全体(再帰的に)を見て検出されます。この値のデフォルトが機能しない場合、setup.cfgで設定オプションを変更してプロジェクト内で変更できます。

[tool:pytest]
looponfailroots = transformers tests

または pytest.ini/tox.ini ファイル:

[pytest]
looponfailroots = transformers tests

ファイルの変更を探すことは、iniファイルのディレクトリを基準にして指定されたディレクトリ内でのみ行われます。

pytest-watch は、この機能の代替実装です。

Skip a test module

特定のテストモジュールを除外してすべてのテストモジュールを実行したい場合、実行するテストの明示的なリストを指定することができます。例えば、test_modeling_*.py テストを除外してすべてを実行するには次のようにします:

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

Clearing state

CIビルドおよび速度に対する隔離が重要な場合(キャッシュに対して)、キャッシュをクリアする必要があります:

pytest --cache-clear tests

Running tests in parallel

前述のように、make testpytest-xdist プラグインを介してテストを並列実行します(-n X 引数、例: -n 2 で2つの並列ジョブを実行)。

pytest-xdist--dist= オプションを使用すると、テストがどのようにグループ化されるかを制御できます。--dist=loadfile は同じファイルにあるテストを同じプロセスに配置します。

テストの実行順序が異なり予測不可能であるため、pytest-xdist を使用してテストスイートを実行すると失敗が発生する場合(つまり、いくつかの未検出の連動テストがある場合)、pytest-replay を使用してテストを同じ順序で再生し、その後、失敗するシーケンスを最小限にするのに役立ちます。

Test order and repetition

潜在的な相互依存性や状態に関連するバグ(ティアダウン)を検出するために、テストを複数回、連続して、ランダムに、またはセットで繰り返すことは有用です。そして、単純な複数回の繰り返しは、DLのランダム性によって明らかになるいくつかの問題を検出するのに役立ちます。

Repeat tests

pip install pytest-flakefinder

そして、すべてのテストを複数回実行します (デフォルトでは 50 回)。

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

このプラグインは、pytest-xdist-n フラグでは動作しません。

別のプラグイン pytest-repeat もありますが、これは unittest では動作しません。

Run tests in a random order

pip install pytest-random-order

重要: pytest-random-order が存在すると、テストは自動的にランダム化されます。設定の変更や変更は必要ありません。 コマンドラインオプションは必須です。

前に説明したように、これにより、結合されたテスト (1 つのテストの状態が別のテストの状態に影響を与える) の検出が可能になります。いつ 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

特定のテストのリストを使用しない場合、またはまったくリストを使用しない場合、同じテストの正確な順序を再現します。テストのリストを手動で絞り込み始めると、シードに依存せず、テストが失敗した正確な順序で手動でリストを指定する必要があります。これには、--random-order-bucket=none を使用してランダム化を無効にするようpytestに指示する必要があります。例えば、次のようにします:

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 が暗黙的に適用され、モジュールレベルでファイルをシャッフルします。また、classpackageglobal、およびnone レベルでシャッフルすることもできます。詳細については、そのドキュメンテーションを参照してください。

別のランダム化の代替手段は、pytest-randomly です。このモジュールは非常に似た機能/インターフェースを持っていますが、pytest-random-order で利用可能なバケットモードを持っていません。インストール後に自動的に有効になるという同じ問題があります。

Look and feel variations

pytest-sugar

pytest-sugar は、外観と操作性を向上させ、プログレスバーを追加し、即座に失敗したテストとアサーションを表示するプラグインです。インストール後に自動的にアクティブ化されます。

pip install pytest-sugar

これを使用せずにテストを実行するには、次を実行します。

pytest -p no:sugar

またはアンインストールします。

Report each sub-test name and its progress

pytest による単一またはグループのテストの場合 (pip install pytest-pspec の後):

pytest --pspec tests/test_optimization.py

Instantly shows failed tests

pytest-instafail では、失敗とエラーが即座に表示されます。 テストセッションが終了するまで待機します。

pip install pytest-instafail
pytest --instafail

To GPU or not to GPU

GPU が有効な設定で、CPU のみモードでテストするには、CUDA_VISIBLE_DEVICES=""を追加します。

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

または、複数の GPU がある場合は、pytest でどれを使用するかを指定できます。たとえば、 2 番目の GPU GPU 01 がある場合は、次を実行できます。

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

これは、異なるGPUで異なるタスクを実行したい場合に便利です。

一部のテストはCPUのみで実行する必要があり、他のテストはCPU、GPU、またはTPUで実行する必要があり、また別のテストは複数のGPUで実行する必要があります。次のスキップデコレーターは、テストのCPU/GPU/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 gpus | decorator | |--------+--------------------------------| | >= 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 |

たとえば、使用可能な GPU が 2 つ以上あり、pytorch がインストールされている場合にのみ実行する必要があるテストを次に示します。

@require_torch_multi_gpu
def test_example_with_multi_gpu():

テストに tensorflow が必要な場合は、require_tf デコレータを使用します。例えば:

@require_tf
def test_tf_thing_with_tensorflow():

これらのデコレータは積み重ねることができます。たとえば、テストが遅く、pytorch で少なくとも 1 つの 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

Testing with a specific PyTorch backend or device

特定のtorchデバイスでテストスイートを実行するには、TRANSFORMERS_TEST_DEVICE="$device" を追加します。ここで $device は対象のバックエンドです。例えば、CPUでテストするには以下のようにします:

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

この変数は、mpsなどのカスタムまたはあまり一般的ではない PyTorch バックエンドをテストするのに役立ちます。また、特定の GPU をターゲットにしたり、CPU 専用モードでテストしたりすることで、CUDA_VISIBLE_DEVICESと同じ効果を達成するために使用することもできます。

特定のデバイスでは、初めて「torch」をインポートした後、追加のインポートが必要になります。これは、環境変数 TRANSFORMERS_TEST_BACKEND を使用して指定できます。

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

Distributed training

pytest は直接的に分散トレーニングを処理することはできません。試みると、サブプロセスは正しい処理を行わず、自分自身が pytest であると思い込んでテストスイートをループで実行し続けます。ただし、通常のプロセスを生成し、それから複数のワーカーを生成し、IOパイプを管理するプロセスを生成すれば機能します。

これを使用するいくつかのテストがあります:

実行ポイントにすぐに移動するには、これらのテスト内で execute_subprocess_async 呼び出しを検索してください。

これらのテストを実行するには、少なくとも2つのGPUが必要です:

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

Output capture

テストの実行中に、stdout および stderr に送信された出力はキャプチャされます。テストまたはセットアップメソッドが失敗した場合、通常、それに対応するキャプチャされた出力が失敗のトレースバックと共に表示されます。

出力のキャプチャを無効にし、stdoutstderr を通常通りに取得するには、-s または --capture=no を使用してください:

これらのテストを実行するには少なくとも2つのGPUが必要です:

pytest -s tests/utils/test_logging.py

テスト結果を JUnit 形式の出力に送信するには:

py.test tests --junitxml=result.xml

Color control

色を持たないようにする(例:黄色のテキストを白い背景に表示すると読みにくいです):

pytest --color=no tests/utils/test_logging.py

Sending test report to online pastebin service

テスト失敗ごとに URL を作成します。

pytest --pastebin=failed tests/utils/test_logging.py

これにより、テスト実行情報がリモートのPasteサービスに送信され、各エラーに対してURLが提供されます。通常通りテストを選択するか、たとえば特定のエラーのみを送信したい場合は -x を追加で指定できます。

テストセッション全体のログに対するURLを作成する方法:

pytest --pastebin=all tests/utils/test_logging.py

Writing tests

🤗 transformersのテストは unittest を基にしていますが、 pytest で実行されるため、ほとんどの場合、両方のシステムの機能を使用できます。

こちらでサポートされている機能を読むことができますが、重要なことは、ほとんどの pytest のフィクスチャが動作しないことです。パラメータ化も同様ですが、似たような方法で動作する parameterized モジュールを使用しています。

Parametrization

同じテストを異なる引数で複数回実行する必要があることがよくあります。これはテスト内部から行うこともできますが、その場合、そのテストを単一の引数セットで実行する方法はありません。

# 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

transformersの開発者依存関係にすでに含まれているモジュールparameterized は、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]

前の例と同様に。

Files and directories

テストの中で、現在のテストファイルからの相対位置を知る必要があることがよくあります。しかし、これは簡単なことではありません。なぜなら、テストは複数のディレクトリから呼び出されるか、異なる深さのサブディレクトリに存在することがあるからです。transformers.test_utils.TestCasePlus というヘルパークラスは、すべての基本パスを整理し、簡単にアクセスできるようにすることで、この問題を解決します。

  • pathlib オブジェクト(すべて完全に解決されたもの):

    • test_file_path - 現在のテストファイルのパス、つまり __file__
    • test_file_dir - 現在のテストファイルを含むディレクトリ
    • tests_dir - tests テストスイートのディレクトリ
    • examples_dir - examples テストスイートのディレクトリ
    • repo_root_dir - リポジトリのディレクトリ
    • src_dir - 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

Temporary files and directories

一意の一時ファイルとディレクトリの使用は、並列テストの実行には欠かせません。これにより、テストがお互いのデータを上書きしないようにします。また、これらを作成した各テストの終了時に一時ファイルとディレクトリが削除されることを望みます。そのため、これらのニーズを満たすパッケージである 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 などのファイルシステムの重要な部分が削除されないように、常に ./ から始まるパスを渡してください。

各テストは複数の一時ディレクトリを登録でき、要求がない限りすべて自動で削除されます。

Temporary sys.path override

別のテストからインポートするために一時的に 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

Skipping tests

これは、バグが見つかり、新しいテストが作成された場合であっても、バグがまだ修正されていない場合に役立ちます。メインリポジトリにコミットできるようにするには、make test の実行中にそれをスキップする必要があります。

メソッド:

  • skip は、テストが特定の条件が満たされた場合にのみパスすることを期待しており、それ以外の場合は pytest がテストの実行をスキップします。一般的な例は、Windows専用のテストを非Windowsプラットフォームでスキップする場合、または現在利用できない外部リソースに依存するテストをスキップする場合です(例: データベースが利用できない場合)。

  • xfail は、何らかの理由でテストが失敗することを期待しています。一般的な例は、まだ実装されていない機能のテストや、まだ修正されていないバグのテストです。テストが予想される失敗にもかかわらずパスした場合(pytest.mark.xfailでマークされたテスト)、それはxpassとしてテストサマリーに報告されます。

これらの2つの間の重要な違いの1つは、skip はテストを実行しない点であり、xfail は実行します。したがって、バグのあるコードが他のテストに影響を与える場合は、xfail を使用しないでください。

Implementation

  • テスト全体を無条件にスキップする方法は次のとおりです:
@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):

詳細、例、および方法についての詳細はこちらを参照してください。

Slow tests

テストライブラリは着実に成長しており、テストの一部は数分かかります。そのため、CIでテストスイートの完了を待つのは1時間待つ余裕がないことがあります。したがって、いくつかの例外を除いて、遅いテストは以下の例のようにマークすべきです:

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を提出する前に自分のマシンで遅いテストを実行する重要性を意味しています。

どのテストを遅いテストとしてマークすべきかを選択するための、おおまかな意思決定メカニズムが次に示されています:

  • テストがライブラリの内部コンポーネントの1つに焦点を当てている場合(例: モデリングファイル、トークン化ファイル、パイプライン)、そのテストは遅いテストスイートで実行する必要があります。それがライブラリの他の側面、たとえばドキュメンテーションや例に焦点を当てている場合、それらのテストは遅いテストスイートで実行する必要があります。そして、このアプローチを洗練させるために例外を設ける必要があります。

  • 重いウェイトセットや約50MB以上のデータセットをダウンロードする必要があるすべてのテスト(例: モデル統合テスト、トークナイザ統合テスト、パイプライン統合テスト)は遅いテストとして設定する必要があります。新しいモデルを追加する場合、統合テスト用にランダムなウェイトを持つ小さなバージョンを作成し、ハブにアップロードする必要があります。これについては以下の段落で詳しく説明します。

  • 特に高速化されていないトレーニングを行う必要があるすべてのテストは遅いテストとして設定する必要があります。

  • 一部の「遅い」であるべきでないテストが非常に遅い場合、およびそれらを @slow として設定する必要がある場合には例外を導入できます。大容量のファイルをディスクに保存および読み込みする自動モデリングテストは、@slow としてマークされたテストの良い例です。

  • CIで1秒未満でテストが完了する場合(ダウンロードを含む)、それは通常のテストであるべきです。

すべての非遅いテストは、さまざまな内部要素を完全にカバーする必要がありますが、高速である必要があります。たとえば、特別に作成された小さなモデル(レイヤー数が最小限で、語彙サイズが小さいなど)を使用して、かなりのカバレッジを実現できます。その後、@slow テストでは大規模な遅いモデルを使用して質的なテストを実行できます。これらを使用するには、以下のように tiny モデルを探してください:

grep tiny tests examples

スクリプトの例があり、これにより tiny-wmt19-en-de のような小さなモデルが作成されます。特定のモデルのアーキテクチャに簡単に調整できます。

実行時間を誤って測定することが簡単です。たとえば、巨大なモデルのダウンロードに関するオーバーヘッドがある場合、ローカルでテストするとダウンロードされたファイルがキャッシュされ、ダウンロード時間が計測されなくなります。したがって、CIログの実行速度レポート(pytest --durations=0 tests の出力)を確認してください。

このレポートは、遅いテストとしてマークされていない遅い外れ値や、高速に書き直す必要があるテストを見つけるのにも役立ちます。テストスイートがCIで遅くなり始めた場合、このレポートのトップリストには最も遅いテストが表示されます。

Testing the stdout/stderr output

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/excel を使用する必要があります。 ケース:

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 をキャプチャするもう 1 つのアプローチは、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 をキャプチャする際の重要な潜在的な問題は、通常の print でこれまでに出力された内容をリセットする可能性がある \r 文字が含まれている可能性があることです。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)

また、テストの問題のデバッグを支援するために、デフォルトで、これらのコンテキスト マネージャーは終了時にキャプチャされたストリームを自動的に再生します。 文脈から。

Capturing logger stream

ロガーの出力を検証する必要がある場合は、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"

Testing with environment variables

特定のテストで環境変数の影響をテストしたい場合は、ヘルパー デコレータを使用できます。 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.environPYTHONPATHを設定してインクルードする必要があります。 複数のローカル パス。ヘルパー クラス 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] を使用して、これら 2 つのディレクトリのいずれかを含めます。また、テストが確実に行われるようにするための src ディレクトリも含めます。 現在のリポジトリに対して実行され、最後に、テストが実行される前にすでに設定されていた env[PYTHONPATH] を使用して実行されます。 何かあれば呼ばれます。

このヘルパー メソッドは os.environ オブジェクトのコピーを作成するため、元のオブジェクトはそのまま残ります。

Getting reproducible results

状況によっては、テストのランダム性を削除したい場合があります。同一の再現可能な結果セットを取得するには、 シードを修正する必要があります:

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)

# tf RNG
tf.random.set_seed(seed)

Debugging tests

警告が発生した時点でデバッガーを開始するには、次の手順を実行します。

pytest tests/utils/test_logging.py -W error::UserWarning --pdb

Working with github actions workflows

セルフプッシュのワークフローCIジョブをトリガーするには、以下の手順を実行する必要があります:

  1. transformers のリモートリポジトリで新しいブランチを作成します(フォークではなく、元のリポジトリで行います)。
  2. ブランチの名前は ci_ または ci- で始まる必要があります(main もトリガーしますが、main ではPRを作成できません)。また、特定のパスでのみトリガーされます - このドキュメントが書かれた後に変更された場合に備えて、最新の定義はこちらpush: にあります。
  3. このブランチからPRを作成します。
  4. その後、このジョブがここに表示されます。ジョブはバックログがある場合、すぐに実行されないことがあります。

Testing Experimental CI Features

CI機能のテストは通常のCIの正常な動作に干渉する可能性があるため、新しいCI機能を追加する場合、以下の手順に従う必要があります。

  1. テストが必要なものをテストするための新しい専用のジョブを作成します。
  2. 新しいジョブは常に成功する必要があるため、常にグリーン ✓(詳細は以下参照)を表示する必要があります。
  3. さまざまな種類のPR(ユーザーフォークブランチ、非フォークブランチ、github.com UIから直接ファイルを編集するブランチ、さまざまな強制プッシュなど)が実行されるまでいくつかの日間実行し、実験的なジョブのログを監視します(意図的に常にグリーンになるようになっている全体のジョブの緑ではなく)。
  4. すべてが安定していることが明確になったら、新しい変更を既存のジョブに統合します。

このように、CI機能自体の実験が通常のワークフローに干渉しないようにできます。

では、新しいCI機能が開発中である間、ジョブを常に成功させるにはどうすればいいでしょうか?

TravisCIのような一部のCIは 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の動作に干渉しないようにします。

このプロセス全体は、実験的なステップに対して allow-failure のようなものを設定し、PRの全体のステータスに影響を与えずに失敗させることができれば、はるかに簡単になったでしょう。しかし、前述の通り、現在はCircleCIとGithub Actionsはこの機能をサポートしていません。

この機能に関しての投票や、CIに特有のスレッドでその進捗状況を確認できます:

< > Update on GitHub