Pytestでのテスト入門


テストはコードを実行するコードです.Pythonプロジェクトの新機能の開発を開始すると、その要件をコードとして形式化できます.そうするとき、あなたの実装のコードを使用する方法を文書化するだけでなく、すべてのテストを自動的に実行することもできます.これを支援する1つのそのようなツールはpytest そしてそれはおそらくPythonの宇宙で最も人気のあるテストツールです.

それはすべて断言している


メールアドレスを検証する関数を書いたとしましょう.ここでは簡単にして、正規表現やDNSテストを使用しないでください.代わりに、我々はちょうど1つがあることを確認します@ テストする文字列とラテン文字、数字、および. , - and _ 登場人物
import string
def is_valid_email_address(s):
    s = s.lower()
    parts = s.split('@')
    if len(parts) != 2:
      # Not exactly one at-sign
      return False
    allowed = set(string.ascii_lowercase + string.digits + '.-_')
    for part in parts:
        if not set(part) <= allowed:
          # Characters other than the allowed ones are found
          return False
    return True
今、我々のコードにいくつかの主張があります.たとえば、これらのメールアドレスが有効であることを確認します.
  • [email protected]
  • [email protected]
  • [email protected]
  • 一方、我々の関数はFalse 電子メールアドレスのように
  • not [email protected] (スペースを含む)
  • john.doe (no @ )
  • john,[email protected], )
  • 我々の関数は、実際に我々が期待するような振る舞いをすることを確認できます.
    print(is_valid_email_address('[email protected]'))               # True
    print(is_valid_email_address('[email protected]'))  # True
    print(is_valid_email_address('[email protected]'))     # True
    print(is_valid_email_address('not [email protected]'))          # False
    print(is_valid_email_address('john.doe'))                       # False
    print(is_valid_email_address('john,[email protected]'))           # False
    
    これらのメールアドレスの例はテストケースと呼ばれます.各テストケースについては、ある結果を期待します.テストツールpytest 自動化これらのアサーションをテストすることができます.これらのアサーションを書き留めて
  • コードがどのように使われるかを文書化する
  • 将来の変更がソフトウェアの他の部分を壊さないようにしてください
  • あなたの機能性の可能なエッジケースについて考えてください
  • これを行うには、すべてのテストのための新しいファイルを作成し、そこにいくつかの関数を置く.
    def test_regular_email_validates():
        assert is_valid_email_address('[email protected]')
        assert is_valid_email_address('[email protected]')
        assert is_valid_email_address('[email protected]')
    
    
    def test_valid_email_has_one_at_sign():
        assert not is_valid_email_address('john.doe')
    
    def test_valid_email_has_only_allowed_chars():
        assert not is_valid_email_address('john,[email protected]')
        assert not is_valid_email_address('not [email protected]')
    

    ランニングテスト


    簡単な例


    プロジェクトディレクトリに2つのファイルがあります.validator.py and test_validator.py .
    我々は今すぐに実行することができますpytest コマンドラインから.出力は以下のようになります.
    ============================= test session starts ==============================
    platform darwin -- Python 3.9.6, pytest-7.0.1, pluggy-1.0.0
    rootdir: /Users/bascodes/Code/blogworkspace/pytest-example
    collected 3 items
    
    test_validator.py ...                                                    [100%]
    
    ============================== 3 passed in 0.01s ===============================
    
    pytest 内部に3つのテスト機能を見つけたことを知らせてくださいtest_validator.py そして、これらのすべての機能が渡された... ).
    The 100% インジケータは、我々のバリデータが期待通りに動作することを確信しているので、良い感じを与えます.しかし、導入において概説されるように、バリデータ関数は完全ではない.そして、我々のテストケースもそうです.DNSテストなしでさえ、我々は電子メールアドレスをマークしますjohn.doe@example アドレスのように有効な[email protected] が無効です.
    今、これらのテストケースを加えましょうtest_validator.py
    ...
    def test_valid_email_can_have_plus_sign():
        assert is_valid_email_address('[email protected]')
    
    def test_valid_email_must_have_a_tld():
        assert not is_valid_email_address('john.doe@example')
    
    我々が走るならばpytest 再び、失敗したテストが表示されます.
    ============================= test session starts ==============================
    platform darwin -- Python 3.9.6, pytest-7.0.1, pluggy-1.0.0
    rootdir: /Users/bascodes/Code/blogworkspace/pytest-example
    collected 5 items
    
    test_validator.py ...FF                                                  [100%]
    
    =================================== FAILURES ===================================
    _____________________ test_valid_email_can_have_plus_sign ______________________
    
        def test_valid_email_can_have_plus_sign():
    >       assert is_valid_email_address('[email protected]')
    E       AssertionError: assert False
    E        +  where False = is_valid_email_address('[email protected]')
    
    test_validator.py:17: AssertionError
    _______________________ test_valid_email_must_have_a_tld _______________________
    
        def test_valid_email_must_have_a_tld():
    >       assert not is_valid_email_address('john.doe@example')
    E       AssertionError: assert not True
    E        +  where True = is_valid_email_address('john.doe@example')
    
    test_validator.py:20: AssertionError
    =========================== short test summary info ============================
    FAILED test_validator.py::test_valid_email_can_have_plus_sign - AssertionErro...
    FAILED test_validator.py::test_valid_email_must_have_a_tld - AssertionError: ...
    ========================= 2 failed, 3 passed in 0.05s ==========================
    
    我々が2を得たことに注意してくださいFF アウト3に加えて... つのテスト関数が失敗したことを示すドット.
    加えて、我々は新しいFAILURES 我々のテストで失敗した点で詳細に説明する我々の出力のセクション.デバッグにはかなり役立つ.

    テスト設計に関するノート


    私たちの小さなバリデータの例は、テストを設計することの重要性の証です.
    最初にバリデーター関数を書き、いくつかのテストケースを思いついた.すぐに我々はこれらのテストケースは包括的ではないことに気づいた.代わりに、メールアドレスの検証の重要な側面を逃しました.
    テスト駆動開発(TDD)について聞いたことがあります.TDDは、あなたのテストケースを最初に書いて、すべてのテストケースをカバーしていると感じる前に、機能の実装を開始しないことで、あなたの要求を正しく取得します.この考え方は常に良い考えでしたが、ソフトウェアプロジェクトが複雑さを増してきたので、時間をかけてさらに重要性を増してきました.
    私はすぐに深さにそれをカバーするTDDについての別のブログ記事を書きます.

    構成


    通常、プロジェクトのセットアップは、その中のバリデータ関数を持つ単一のファイルよりもはるかに複雑です.
    プロジェクトのPythonパッケージ構造を持っているかもしれません.あるいは、コードがデータベースのような外部依存関係に依存する場合があります.

    器具


    セットアップとティアダウン


    あなたは、異なる文脈で用語Fixtureを使用したかもしれません.たとえば、Django Webフレームワークの場合、fixtureはデータベースに読み込まれる初期データのコレクションを参照します.しかし、pytest 'sコンテキスト、fixtureはpytest 前と実際のテスト機能の後.
    このような関数をpytest.fixture() デコレータ.我々は、内部でこれをしますtest_validator.py 今すぐファイル.
    import pytest
    
    @pytest.fixture()
    def database_environment():
        setup_database()
        yield
        teardown_database()
    
    データベースを設定し、それを引き裂くことは同じfixtureで起こります.The yield キーワードはどこの部分をinidcatespytest 実際のテストの実行
    フィクスチャを実際にテストのいずれかで使用するには、単純に引数としてフィクスチャの名前を追加しますtest_validator.py ):
    def test_world(database_environment):
        assert 1 == 1
    

    備品からデータを取得する


    代わりにyield fixture関数は任意の値を返すこともできます.
    import pytest
    
    @pytest.fixture()
    def my_fruit():
        return "apple"
    
    もう一度、テスト関数からそのフィクスチャを要求するには、fixtures名をパラメータとして提供します.
    def test_fruit(my_fruit):
        assert my_fruit == "apple"
    

    設定ファイル

    pytest これらのファイルからプロジェクト固有の設定を読み込むことができます.
  • pytest.ini
  • tox.ini
  • setup.cfg
  • 使用するファイルは、プロジェクトで使用する他のツールに依存します.プロジェクトをパッケージ化した場合は、setup.cfg ファイル.異なる環境でコードをテストするためにTOXを使用すると、pytest の設定tox.ini ファイル.The pytest.ini ファイルを使用する場合は、任意の追加ツールを利用したくない場合に使用することができますがpytest .
    設定ファイルは、これらの3つのファイルタイプごとにほとんど同じです.
    使用するpytest.ini and tox.ini :
    [pytest]
    addopts = ​-rsxX -l --tb=short --strict​
    
    **使用している場合setup.cfg ファイルは、唯一の違いは[pytest] セクションtool: 以下のように:
    [tool:pytest]
    addopts = ​-rsxX -l --tb=short --strict​
    

    懺悔する。パイ


    テストファイルを含む各フォルダにはconftest.py 読み込むファイルpytest . これは、これらの異なるファイル間で共有できるようにカスタムの備品を配置するのに良い場所です.
    The conftest.py ファイル( s )はpytest プロジェクトごとに.
    共有備品は別として、外部フックやプラグインや修飾子をPATH 使用するpytest テストと実装コードを発見する.

    CLI / PDB


    開発中に、主にあなたの実装前にテストを書いたとき.pytest デバッグのための有益なツールです.
    最も便利なコマンドラインオプションを見てみましょう.

    つのテストを実行するだけ


    つの特定のテストのみを実行する場合は、test_ ファイルが入っており、その関数の名前は以下の通りです:
    pytest test_validator.py::test_regular_email_validates
    

    収集のみ


    すべてのテスト関数を実行するのではなく、テストコレクションのリストを持っています.
    pytest --collect-only
    
    ============================= test session starts ==============================
    platform darwin -- Python 3.9.6, pytest-7.0.1, pluggy-1.0.0
    rootdir: /Users/bascodes/Code/blogworkspace/pytest-example
    collected 5 items
    
    <Module test_validator.py>
      <Function test_regular_email_validates>
      <Function test_valid_email_has_one_at_sign>
      <Function test_valid_email_has_only_allowed_chars>
      <Function test_valid_email_can_have_plus_sign>
      <Function test_valid_email_must_have_a_tld>
    
    ========================== 5 tests collected in 0.01s ==========================
    

    最初のエラーで終了する


    力があるpytest 失敗した後のテストを停止するには、次の手順に従います.
    pytest -x
    

    最後に失敗したテストのみを実行する


    前回失敗したテストのみを実行したい場合は、--lf フラグ:
    pytest --lf
    

    すべてのテストを実行する


    pytest --ff
    

    出力中のローカル変数の値を表示する


    いくつかのローカル変数を使用してより複雑なテスト関数を設定すると、pytest これらのローカル変数を-l フラグ.
    次のようにテスト機能を書き直しましょう.
    ...
    def test_valid_email_can_have_plus_sign():
        email = '[email protected]'
        assert is_valid_email_address('[email protected]')
    ...
    
    Then,
    pytest -l
    
    を返します.
    ============================= test session starts ==============================
    platform darwin -- Python 3.9.6, pytest-7.0.1, pluggy-1.0.0
    rootdir: /Users/bascodes/Code/blogworkspace/pytest-example
    collected 5 items
    
    test_validator.py ...FF                                                  [100%]
    
    =================================== FAILURES ===================================
    _____________________ test_valid_email_can_have_plus_sign ______________________
    
        def test_valid_email_can_have_plus_sign():
            email = '[email protected]'
    >       assert is_valid_email_address('[email protected]')
    E       AssertionError: assert False
    E        +  where False = is_valid_email_address('[email protected]')
    
    email      = '[email protected]'
    
    test_validator.py:18: AssertionError
    _______________________ test_valid_email_must_have_a_tld _______________________
    
        def test_valid_email_must_have_a_tld():
    >       assert not is_valid_email_address('john.doe@example')
    E       AssertionError: assert not True
    E        +  where True = is_valid_email_address('john.doe@example')
    
    
    test_validator.py:21: AssertionError
    =========================== short test summary info ============================
    FAILED test_validator.py::test_valid_email_can_have_plus_sign - AssertionErro...
    FAILED test_validator.py::test_valid_email_must_have_a_tld - AssertionError: ...
    ========================= 2 failed, 3 passed in 0.09s ==========================
    

    PyTestを使用したテスト

    pdb はPythonに組み込まれたコマンドラインデバッガです.テスト関数のコードをデバッグするにはPyTestを使用できます.
    あなたが始めるならばpytest with --pdb , それは始まりますpdb テストの例外が発生した直後のデバッグセッション.ほとんどの場合、例外を送出する前にコードの各行を検査したい場合は特に役に立ちません.
    もう一つのオプションは--trace フラッグフォーpytest 各テスト関数の最初の行でブレークポイントを設定します.テストがたくさんあるなら、これは少し不便になるかもしれません.それで、デバッグ目的のために、良い組合せはそうです--lf --trace デバッグセッションを開始するpdb 失敗した最後のテストの先頭に
    pytest --lf --trace
    
    ============================= test session starts ==============================
    platform darwin -- Python 3.9.6, pytest-7.0.1, pluggy-1.0.0
    rootdir: /Users/bascodes/Code/blogworkspace/pytest-example, configfile: pytest.ini
    collected 2 items
    run-last-failure: rerun previous 2 failures
    
    test_validator.py
    >>>>>>>>>>>>>>>>>>>> PDB runcall (IO-capturing turned off) >>>>>>>>>>>>>>>>>>>>>
    > /Users/bascodes/Code/blogworkspace/pytest-example/test_validator.py(17)test_valid_email_can_have_plus_sign()
    -> email = '[email protected]'
    (Pdb)
    

    シー・シーディー


    最新のソフトウェアプロジェクトでは、ソフトウェアはテスト駆動開発原則に従って開発され、自動化されたテストを含む連続的な統合/連続配置パイプラインを介して配信されます.
    典型的なセットアップはmain/master すべてのテスト関数が通過しない限り、分岐は拒否されます.
    あなたが使用についてもっと知りたいならばpytest CI/CD環境では、私はそのトピックの新しい記事を計画しているように調整滞在.