はじめに
何事もまずは標準装備の機能からちゃんと使えるようになろうと思って、PythonのUnittestをちょくちょく触っていたんですが、案件ではpytestを使っています。pytestの書き方にも慣れてきて、毎日読んだり書いたりしていますが、受け身一方で身の回りにあるコード例しか知らない。これはあんまり良くないと思って、ちゃんと自力でディグって使えそうなコマンドとか機能を発掘しようというと思い立ちました。あと、pytest.Parameterize
と仲良くなりたくて。Parameterize
が本題です。
成果物
pytest.Parametarize
の基本
ここに関数があります。StringをIntに変換してくれるだけのやつです。世界一作る意味の無い関数の内の1つですね。
def convert_str_into_int(x): return int(x)
さて、この関数をテストするコードを愚直に書いてみます。
@pytest.fixture def target(): return convert_str_into_int def test_convert_str_into_int(target): expected = 10 actual = target("10") assert actual == expected
そして pytest
で実行だっ!
$ pytest ============================= test session starts ============================== platform darwin -- Python 3.7.1, pytest-4.1.1, py-1.7.0, pluggy-0.8.1 rootdir: /Users/kai/Github/parametrize, inifile: collected 1 item test_parametrizing.py . [100%] =========================== 1 passed in 0.01 seconds ===========================
ちゃんとPassedになりました。
parametrizingする
ただし、10でテストが通ったからって100で通るとは限らない。0ならどうだろう。-100なら。999999999999とか、いろんなケースでテストしたいときがあります。そんなときにparametrize
が使えるんですね。いま列挙したケースをparametrize
を使って書くと以下の通りになります。
@pytest.mark.parametrize("inputs, expected", [ ("10", 10), ("100", 100), ("0", 0), ("-1", -1), ("999999999", 999999999)]) def test_convert_str_into_int(inputs, expected, target): actual = target(inputs) assert actual == expected
実行結果はちゃんとPassedしています。
$ pytest ============================= test session starts ============================== platform darwin -- Python 3.7.1, pytest-4.1.1, py-1.7.0, pluggy-0.8.1 rootdir: /Users/kai/Github/parametrize, inifile: collected 5 items test_parametrizing.py ..... [100%] =========================== 5 passed in 0.04 seconds ===========================
テストが通るってしあわせですよね。
パラメータにidをつける
あんまり長ったらしいパラメータをデコレーション内に書くとコードが読みづらくなるので、リファクタリングします。ついでに、パラメータごとにidを設定してみましょう。以下のようにパラメータとidを準備します。
test_data = [ ("10", 10), ("100", 100), ("0", 0), ("-1", -1), ("999999999", 999999999) ] test_ids = [ "10", "100", "0", "-1", "99999999", ]
そしてpytestのデコレーション側で上で定義した test_data
と test_ids
を指定します。
@pytest.mark.parametrize("inputs, expected", test_data, ids=test_ids) def test_convert_str_into_int(target, inputs, expected): actual = target(inputs) assert actual == expected
idはテストが失敗した時にどのパラメータで失敗したかをすぐに識別するのに役に立ちます。また、pytest --collect-only
を使うと有効なテストのみをpytestが収集して表示してくれるのでidを確認することができます。pytest --collect-only
をしてみます。
============================= test session starts ============================= platform darwin -- Python 3.7.1, pytest-4.1.1, py-1.7.0, pluggy-0.8.1 rootdir: /Users/kai/Github/parametrize, inifile: collected 5 items <Module test_parametrizing.py> <Function test_convert_str_into_int[10]> <Function test_convert_str_into_int[100]> <Function test_convert_str_into_int[0]> <Function test_convert_str_into_int[-1]> <Function test_convert_str_into_int[99999999]> ========================= no tests ran in 0.01 seconds ==========================
ちゃんと []
の中にidが表示されています。
Fixtureとパラメータを一緒に使う
Fixtureを使うテストにもparametrize
で値を与えることができます。またparametize
で設定した値を、Fixture内で処理してからテストしたい時があります。そういうときは引数にダイレクトに値を与えるのではなく、Fixtureを通して引数として設定するためのオプションindirect
を使います。
@pytest.fixture(scope='function') def greet(request): if request.param == 'dog': return 'bow-bow' if request.param == 'cat': return 'meow' return '... ...' params = [ ('dog', 'bow-bow'), ('cat', 'meow'), ('cow', '... ...') ] @pytest.mark.parametrize('greet, expected', params, indirect=['greet']) def test_indirect(greet, expected): assert greet == expected
indirect
はキーワード引数で指定したもののみに適応されます。今回はgreet
フィクスチャに適応されています。もし、indirect
がなければ、greet
にはdog
, cat
, cow
がそのまま引数として渡されてしまいます。
parametrize
はこれで終わり。
pytestの小ネタ
なんか面白いコマンドや機能はないかなと、調べてみました。
Skipの理由を書く
たまにテストをSkipすることがある。@pytest.mark.skip
でスキップすることができる。どうせならSkipの理由を表示すると良いんじゃないかなぁと思う。次のように書ける。
@pytest.mark.skip(reason="Skip this test for test")
実行してみる。実行コマンドに-rs
をつけることを忘れちゃいけない。
$ pytest -rs ============================= test session starts ============================== platform darwin -- Python 3.7.1, pytest-4.1.1, py-1.7.0, pluggy-0.8.1 rootdir: /Users/kai/Github/parametrize, inifile: collected 3 items test_parametrizing.py sss [100%] =========================== short test summary info ============================ SKIP [3] test_parametrizing.py:30: Skip this test for test ========================== 3 skipped in 0.02 seconds ===========================
条件に応じてSkipする
これはいまいち使い所がわからないが、いつか使うかもしれない。
@pytest.mark.skipif( os.environ['ENV'] == 'dev', reason="Skip this test for test" )
このようにスキップする条件を書くことができる。
タグ付けしてみる
テストに好きな名前のタグをつけることができる。
@pytest.mark.hoge
hoge
というタグをつけてみた。hoge
というタグのついたテストだけを実行したいなら以下のコマンドで実行できる。
pytest -m hoge
逆にhoge
のタグがついたテストを実行したくないときは次のように実行すればよい。
pytest -m "not hoge"