Pytestの使い方

*Pytestとは

Pythonで書かれたテストライブラリです。
https://docs.pytest.org/en/latest/
他にunittestなどもありますが、他のライブラリよりも失敗時のエラー表示が詳細で見やすかったり、実行時のコマンドにオプションをつけることでテストを並列に実行したり、失敗したテストだけを実行したりといったことができます。
unittestは標準で入っていますがpytestは入っていないため、インストールする必要があります。
$ pip install pytest


*Pytestの書き方

サンプルとして簡単なFizzBuzzプログラムを書いてみました。
#!/usr/bin/python  
# -*- coding: utf-8 -*-  
  
  
def fizzbuzz(num):  
    if num % 3 == 0 and num % 5 == 0:  
        return "FizzBuzz"  
  elif num % 3 == 0:  
        return "Fizz"  
  elif num % 5 == 0:  
        return "Buzz"  
  else:  
        return str(num)
このプログラムに問題がないか、Pytestを使ったテストコードを書いてみます。
テストを作成する際、テストクラス名をTest〜、メソッド名をtest〜にしないとテストとして認識してくれないようです。
# -*- coding: utf-8 -*-
import pytest  
from FizzBuzz import fizzbuzz  
  
  
class TestFizzBuzz(object):  
    def test_success(self):  
        actual = fizzbuzz(15)  
        assert actual == "FizzBuzz"  
  
  
if __name__ == '__main__':  
    pytest.main()

ちなみに、このテストをIntellijで実行するとき、if name == ‘main’: が書いてある箇所を選択して実行しないと、Pytestとして実行してくれませんでした。

※ if name == ‘main’:

Pythonスクリプトを直接実行した場合、name 変数の中には自動で ‘main’ という値が代入されるので、これは直接実行された場合に呼ばれる処理になります。(他プログラムから呼び出された場合はFalseになって実行されません)


*Parametrize

pytest.mark.parametrize デコレーターで入力値と期待値を渡すことで、その値を使ったテストを繰り返し実行してくれます。
# -*- coding: utf-8 -*-  
  
import pytest  
from FizzBuzz import fizzbuzz  
  
  
class TestFizzBuzz(object):  
    @pytest.mark.parametrize("num, expected", [  
        (1, "1"),  
        (3, "Fizz"),  
        (5, "Buzz"),  
        (15, "FizzBuzz")  
    ])  
    def test_success(self, num, expected):  
        result = fizzbuzz(num)  
        assert result == expected  
  
  
if __name__ == '__main__':  
    pytest.main()


*Fixture

Fixtureを使ってテストデータを作成することができます。
# -*- coding: utf-8 -*-  
  
import pytest  
from FizzBuzz import fizzbuzz  
  
  
@pytest.fixture(scope="module", autouse=True)  
def fixture_1():  
    return "1"  
  
  
@pytest.fixture(scope="module", autouse=True)  
def fixture_fizz():  
    return "Fizz"  
  
  
@pytest.fixture(scope="module", autouse=True)  
def fixture_buzz():  
    return "Buzz"  
  
  
@pytest.fixture(scope="module", autouse=True)  
def fixture_fizz_buzz():  
    return "FizzBuzz"  
  
  
class TestFizzBuzz(object):  
    @pytest.mark.parametrize("num, expected", [  
        (1, fixture_1()),  
        (3, fixture_fizz()),  
        (5, fixture_buzz()),  
        (15, fixture_fizz_buzz())  
    ])  
    def test_success(self, num, expected):  
        result = fizzbuzz(num)  
        assert result == expected  
  
  
if __name__ == '__main__':  
    pytest.main()

@pytest.fixtureデコレーターのscopeパラメータには、
session > module > class > function を指定することができます。
(sessionとmoduleは、1つのテストモジュールを実行する際は同じスコープになります。)
@pytest.fixture()autouseパラメータは、セットアップコードを自動実行させるためのもので、特定のscopeを設定する場合はTrueにしておきます。


*感想

今回のような場合は、Fixtureを使うとコードが増えて逆に見辛くなってしまいましたが、テストケースが多い場合は便利そうです。
今までunittestを使っていましたが、Pytestのほうがエラーが見やすく便利なデコレーターがあるので、今後はPytestを積極的に使っていこうと思っています。