#author("2018-10-08T12:20:49+00:00","","") * pytest入門 [#e9ffd5bd] #setlinebreak(on); #html(<style>div * { line-height:1.5;}</style>) #contents ** インストール [#q4177cd9] #html(<div style="padding-left:10px;">) #myterm2(){{ pip install pytest pip install pytest-cov }} #html(</div>) ** ファイル/フォルダ構成 [#f0395e6c] #html(<div style="padding-left:10px;">) https://docs.pytest.org/en/latest/goodpractices.html#choosing-a-test-layout-import-rules pytestのドキュメントにはいくつかの構成が記載されている。 規模や用途に応じて選択すれば良いと思うが、src と tests とフォルダ分けする場合のPATHの指定方法がいまいち良くわからない。 テストコード自体をどこから探すのかは、pytest.ini または pytest 実行時のオプションで testpaths を指定すれば良いらしいが、 テスト対象のソースの格納先をどのように指定するのか良くわからない。(現時点では深く追ってないので詳細不明) #html(<div style="padding:0 40px 0 10px; background:#efefef;border: 1px solid #333;display:inline-block;vertical-align:top;">) setup.py mypkg/ ... tests/ __init__.py foo/ __init__.py test_view.py bar/ __init__.py test_view.py #html(</div>) #html(<div style="padding:0 40px 0 10px; background:#efefef;border: 1px solid #333;display:inline-block;vertical-align:top;">) setup.py src/ mypkg/ __init__.py app.py view.py tests/ __init__.py foo/ __init__.py test_view.py bar/ __init__.py test_view.py #html(</div>) *** セットアップコードと conftest.py [#q5d6c439] #html(<div style="padding-left:10px;">) #html(</div>) #html(</div>) // ファイル/フォルダ構成 ** テストコードの基本形 [#kcb84353] #html(<div style="padding-left:10px;">) 以下の命名規則に従っていれば、テストコードとして認識される。 - ファイル名のプレフィックスに test_ または サフィックスに _test を付与する。 - テストメソッドのプレフィックスに test_ または サフィックスに _test を付与する。 例) #mycode2(){{ def test_sample_function1(): print("test_sample_function1") assert True def test_sample_function2(): print("test_sample_function2") assert False class TestSampleClass(): def test_class_function(): print("test_class_function1") assert True }} #html(</div>) // テストコードの基本形 ** assert の書き方 [#x11919d2] #html(<div style="padding-left:10px;">) https://docs.pytest.org/en/latest/assert.html *** 基本形 [#t6cbd22c] #html(<div style="padding-left:10px;">) #mycode2(){{ assert 条件(※) [, エラーメッセージ] }} ※ True の時に成功となる。 #html(</div>) *** 文字列マッチング [#w2ac605a] #html(<div style="padding-left:10px;">) #mycode2(){{ assert 'match string' in str(result) }} #html(</div>) *** 例外を期待する場合 [#v34ef498] #html(<div style="padding-left:10px;">) #mycode2(){{ with pytest.raises(ZeroDivisionError): 1 / 0 }} &br; #mycode2(){{ with pytest.raises(ValueError, match=r'.* 123 .*'): myfunc() }} #html(</div>) #html(</div>) // assert の書き方 ** fixtureの利用(テスト前後に行う処理) [#v69ba4d3] #html(<div style="padding-left:10px;">) fixtureを使用してテストの前後に行う処理を指定する事ができる。 *** scope について [#q71d9faf] #html(<div style="padding-left:10px;">) #html(<div style="display:inline-block;vertical-align:top">) fixture に scope を指定する事で、どの単位の前処理、後処理なのか明示する事ができる。 |scope|説明|h |session|テスト全体の前後処理| |function|各テストfunction毎に行う前後処理| |class|各テストclass毎に行う前後処理| |module|各テストモジュール毎に行う前後処理| |package|各テストパッケージ毎に行う前後処理| #html(</div>) #html(<div style="display:inline-block;vertical-align:top;padding-left:20px;">) 例) #mycode2(){{ import pytest @pytest.fixture(scope='session', autouse=True) def session_fixture(): print("テスト全体の前処理") yield print("テスト全体の後処理") @pytest.fixture(scope='module', autouse=True) def module_fixture(): print("モジュールの前処理") yield print("モジュールの後処理") @pytest.fixture(scope='class', autouse=True) def class_fixture(): print("クラスの前処理") yield print("クラスの後処理") @pytest.fixture(scope='function', autouse=True) def function_fixture(): print("関数の前処理") yield print("関数の後処理") }} #html(</div>) #html(</div>) // scope について end *** yield について [#x1f624c2] #html(<div style="padding-left:10px;">) fixture 内に yield を記述する事で、テスト後に行う処理を記述する事が出来る。 yield が記述されていないと、全てテストの前に実行されてしまうので注意が必要。 #mycode2(){{ @pytest.fixture(scope='function', autouse=True) def module_fixture(): print("関数の前処理") yield print("関数の後処理") }} #html(</div>) *** fixture からパラメータを受け取る [#ta69f232] #html(<div style="padding-left:10px;">) yield に引数を付けて実行すると、テストコード側で yield の引数を受け取る事ができる。 セットアップコードで生成したインスタンス等を利用してテストコードを実行したい場合等に利用できる。 #mycode2(){{ import pytest import datetime @pytest.fixture(scope='module', autouse=True) def module_fixture1(): print("モジュールの前処理") now = datetime.datetime.now() yield {'now': now} print("モジュールの後処理") def test_fixture_param(module_fixture1): # 引数に対象のfixtureの関数名を指定する #print(f'now: {module_fixture1["now"]') now = module_fixture1['now'] print(f'now: {now}') }} #html(</div>) *** 実行する fixture をテストコード毎に指定する [#l55b07d9] #html(<div style="padding-left:10px;">) 以下の例では、test_fixture_sample2 の時のみ、前処理、後処理として fixture_for_sample2 が実行される。 #mycode2(){{ import pytest @pytest.fixture def fixture_for_sample2(): print("サンプル2用の前処理") yield print("サンプル2用の後処理") def test_fixture_sample1(): print('test_fixture_sample1') def test_fixture_sample2(fixture_for_sample2): # 引数に実行するfixtureを指定する print('test_fixture_sample2') def test_fixture_sample3(): print('test_fixture_sample3') }} #html(</div>) #html(</div>) // テストの前後に行う処理 ** mark の利用 [#b678c566] #html(<div style="padding-left:10px;">) *** 同じテストを値を変えて行う [#gfdb5035] #html(<div style="padding-left:10px;">) pytest.mark.parametrize を使用すると、同じメソッドのテストを引数を得変えてテストする事ができる。 #html(<div style="display:inline-block;">) pytest.mark.parametrize を使用しない場合 #mycode2(){{ import pytest # テスト用に直書き def sample_div(a, b): return a / b def test_sample_div(): assert sample_div(10, 5) == 2 assert sample_div(1, 1) == 1 assert sample_div(10, 1) == 10 with pytest.raises(ZeroDivisionError): sample_div(a, b) }} #html(</div>) #html(<div style="display:inline-block;padding-left:20px;">) pytest.mark.parametrize を使用する場合 #mycode2(){{ import pytest # テスト用に直書き def sample_div(a, b): return a / b @pytest.mark.parametrize('a, b, expected', [ (10, 5, 2), (1, 1, 1), (10, 1, 10), (10, 0, ZeroDivisionError), ]) def test_sample_div(a, b, expected): if type(expected) == int: assert sample_div(a, b) == expected else: with pytest.raises(expected): sample_div(a, b) }} #html(</div>) #html(</div>) *** テストをグループ化する [#sc0ef4e4] #html(<div style="padding-left:10px;">) pytest.mark に続いて任意の名前を付けておくと、テストコードをグループ化する事ができ、グループ毎にテストを実行する事ができる。 #mycode2(){{ import pytest @pytest.mark.service1 def test_sample_func1(): print('test_sample_func1') @pytest.mark.service1 def test_sample_func2(): print('test_sample_func2') @pytest.mark.service2 def test_sample_func3(): print('test_sample_func3') }} 実行例 ( service1 のテストコードのみ実行する ) #myterm2(){{ mytest -m service1 -v }} #html(</div>) #html(</div>) // mark の利用 end ** Tips [#me3d48f1] #html(<div style="padding-left:10px;">) *** 標準出力に表示する [#o9a2ab13] #html(<div style="padding-left:10px;">) #myterm2(){{ pytest -s }} #html(</div>) *** 処理時間を計測する [#ic092ee4] #html(<div style="padding-left:10px;">) #myterm2(){{ pytest --durations=0 pytest -v --duration=0 }} #html(</div>) *** テストケース別に結果を表示する [#s0422d2a] #html(<div style="padding-left:10px;">) #myterm2(){{ pytest -v }} #html(</div>) *** 前回NGだったケースだけテストする [#l20f53f6] #html(<div style="padding-left:10px;">) #myterm2(){{ pytest -v --lf }} #html(</div>) *** 前回NGだったケースからテストする [#bfed747f] #html(<div style="padding-left:10px;">) #myterm2(){{ pytest -v --ff }} #html(</div>) *** 遅いテストケースを見つける [#q5255543] #html(<div style="padding-left:10px;">) #myterm2(){{ pytest -v --duration=5 }} #html(</div>) *** テストのログをファイルに出力する [#sf30545f] #html(<div style="padding-left:10px;">) #myterm2(){{ pytest -v --result-log=tests/log.txt }} #html(</div>) *** カバレッジを確認する [#ad801cfa] #html(<div style="padding-left:10px;">) #myterm2(){{ pytest -v --cov=ディレクトリ }} #html(</div>) *** HTML形式のカバレッジレポートを出力する [#g6612da7] #html(<div style="padding-left:10px;">) #myterm2(){{ pytest -v --cov=ディレクトリ --cov-report=html }} #html(</div>) *** テストで実行されなかった行を表示する [#vbe41a0d] #html(<div style="padding-left:10px;">) #myterm2(){{ pytest -v --cov=ディレクトリ --cov-report=term-missing }} #html(</div>) #html(</div>) // tips end