テスト駆動Pythonをよんだ
タイトルの通り、テストについて本格的に勉強することとした。 最初はちゃんと何ができるかを知るために書籍ベースでやるべきと判断し、唯一のPythonのテスト本である「テスト駆動Python」を読んだ

- 作者: Brian Okken,株式会社クイープ,安井力
- 出版社/メーカー: 翔泳社
- 発売日: 2018/08/29
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (1件) を見る
本書では組み込み関数のunittestではなく、サードパーティライブラリのpytestを使用して解説している。 一部解釈が曖昧なので2段くらいにしてまとめる。(もしくは今回のやつに追記する。)
1-2章まとめ
1. pytestは命名規則がある。
- テストスクリプトの命名は[test_*]とする。
- 関数名も[test_*]とする。
- クラスは[Test*]とする。
2. 簡単なテスト(assertionとraises)
テストの実行
pytestは以下の様にCLIからの実行ができる。
pytest <テストモジュール>
assertionを使う
assert文はassertion <式>
で実行し、式の評価結果がTrue
になることを期待している。
- <式>は一般的な比較演算子( >,<,=等)が使用できる。実はこれがpytestの良いところの一つでunittestの場合は、例えば=
の代わりにassertionEqual
を使用しなければならず、個人的にはこちらの方が直感的に感じる。
- ちなみにbooleanは
assertion <変数>
とかでok(おそらくnotも使えるはず)
例外のテストをraisesで行う
pytest raises(<exception | error>)
を使用する。
- 実際の使用する際にはコンテクストマネージャー(withブロック)を使用する。withブロック内に例外が発生しうる処理を書く。
- また例外メッセージの抽出もできるので例外メッセージののアサーションも可能
具体的には以下のようにして記載する。
def test_sample(): with pytest.raises(TypeError) as err_info: <例外が発生しうる処理> exception_msg = err_info.value.arg[0] #エラーメッセージ抜き出し assertion exception_msg == <予想される例外メッセージ>
3. デコレータによる色々
テストシナリオ(マーキング)作成
@pytest.mark.smoke
のように例えば設定し、コマンド実行時にpytest -m 'smoke'
とすれば、このデコレータをつけたテストのみ実行される(いわゆるシナリオが作成できる。)-
フィクスチャ(テスト専用関数)の利用
@pytest.fixture(autouse=True)
を使用することで、テスト実行を補助する関数を定義できる。例のautouse=True
がモジュール内のすべての関数に適用されるということを示している。初期化処理等を記載するのに良さそう
テストのスキップ
@pytest.mark.skip
や@pytest.mark.skipif
を使用することでデコレータをつけたテストをスキップする。skipifの方は、第一引数に条件(e.g. module.version > '1.1.0')のようにバージョンでスキップするかどうか決める等動的な判定が可能 またskip以外についてはreason引数の指定が強制となっており、-rs
オプションでreasonに渡した値が表示される。
失敗ケースのテスト
@pytest.mark.xfail()
を使用すると、失敗するであろうテストに明示することができる(結果がFAILEDになることが正解となる。)これもskipifと同様に第一引数に条件を渡すことでデコレータの適用条件を指定できる。- xfailによる結果の表示は、
-v
オプションを付けたときxfail
と表示される。XPASS
の表示はxfailデコレータをつけたテストが成功したことを示しており、これはpytest.ini
のxfail_strict=True
にすることで、FAILEDとすることができる。
4. 一部のテストだけを実行
以下の様に指定することでpytestの任意の項目のみテストができる。
- pytest <ディレクトリ> -> ディレクトリ内に含む全てのテストを実施
- pytest <モジュール名> -> モジュール内の全てのテスト関数、クラスを実施
- pytest <モジュール名>::<テストメソッド | テストクラス> ->
::
で指定したメソッドもしくはテストクラスに含まれる全てのメソッドを実行 - pytest <モジュール名>::<テストクラス>::<テストメソッド> -> 指定したテストクラスに含まれるる特定のメソッドのみをテストする。
5. 色々なパラメータによるテスト
- @pytest.mark.parametrize(<arg_name>, <[arg_list]>)でデコレーションすることで複数の引数を全てfor文でテストのパラメータを変えるが如くテストができる。
上記だと分かりにくいので例えば以下のように…
@pytest.parametrize('value', [1,2,3]) def test_type_int(value): assertion type(value)=="int"
valueをそのまま関数の引数に渡す(名前を変えるとエラーになる) この場合1,2,3全てに対してテストが繰り返し行われる。
- <[arg_list]>は別に配列を格納した変数を定義して指定する形でもok
arg_list = [1,2,4] @pytest.mark.paramtrize('value', arg_list) def test_value_type(value): ...
例えば実行すると、以下の表示される。
$ pytest -v test_org.py ==================================================================== test session starts ==================================================================== platform darwin -- Python 3.7.3, pytest-5.2.1, py-1.8.0, pluggy-0.13.0 -- ~/.pyenv/versions/3.7.3/envs/prac_pytest/bin/python3.7 cachedir: .pytest_cache rootdir: ~/work/prac_pytest/src/ch2 collected 4 items test_org.py::test_type_int[1] PASSED [ 25%] test_org.py::test_type_int[2] PASSED [ 50%] test_org.py::test_type_int[3] PASSED [ 75%] test_org.py::test_type_int[4] PASSED [100%] ===================================================================== 4 passed in 0.02s =====================================================================
結果の関数名[value] 結果
valueは今回はvalueがそのまま入ってしまっているが、これは別の値を指定することもできる。
方法は2つあり、
- @pytest.mark.paramtrize()の引数`idsにarg_listと同じ長さの文字列の配列を渡す。
- arg_listの配列をpytest.param()の配列とする。
1のケースは例えば以下のように…
id_param = ['one', 'two', 'three', 'four'] @pytest.mark.parametrize('value', test_param, ids=id_param) def test_type_int_2(value): assert type(value) is int
結果は…
test_org.py::test_type_int_2[one] PASSED [ 25%] test_org.py::test_type_int_2[two] PASSED [ 50%] test_org.py::test_type_int_2[three] PASSED [ 75%] test_org.py::test_type_int_2[four] PASSED
[]の中身が変わっている!
- ちなみに、実行時に
pytest "<テストモジュール名>::<テストメソッド名>[two]"
とすると、該当のパラメータだけテストできる。
2のケースでも結果は同じだが…
pytest_param = [ pytest.param(1, id='one'), pytest.param(2, id='two'), pytest.param(3, id='three'), pytest.param(4, id='four') ] @pytest.mark.parametrize('value', pytest_param) def test_type_int_3(value): assert type(value) is int
これも同じ結果が出力される。
今回は一旦ここできる。 ここまででも色々なテストができると感じた。 次回はフィクスチャについてより詳しくまとめる。