やりたいこと
Pythonのテスト用フレームワークのひとつ「pytest」に入門する。
Pythonのテストについて
Pythonのテスト用フレームワークとしては今回取り上げる「pytest」以外にも、標準モジュールの「unittest」や「doctest」が存在します。
ですが、最近人気なのはサードパーティ製の「pytest」のようです。
それぞれのフレームワークの使い方はこちらの記事、pytestをおすすめする理由はこちらの記事に、わかりやすくまとまっていたので参照ください。
使ってみる
インストール
pip install pytest
基本のテスト
- ターミナルで
pytest
を実行すると、カレントディレクトリ以下に存在するテストがすべて実行されます。 - 特定のテストファイルのみを実行したいときは
pytest tests/test_hoge.py
のようにファイル名を指定します。 - 「test」から始まるファイル・メソッドが、テストのコードであると認識されます。ですので
test_hoge.py
の中にテストしたい関数をdef test_fuga(): ~~
というように定義しましょう。 -
assert
に、検証したい条件式を記述します。ちなみに「assert」とは「断言する」「主張する」という意味らしいです。
# テストしたい関数
def add(a, b):
return a + b
# テスト(成功パターン)
def test_expected():
assert add(3, 5) == 8
# テスト(失敗パターン)
def test_unexpected():
assert add(3, 5) == 10
ターミナルでテストを実行します。
$ pytest tests/test_sample.py
テスト結果は以下のようになりました。
=================================================================== test session starts ===================================================================
platform darwin -- Python 3.8.10, pytest-7.0.1, pluggy-1.0.0
rootdir: /Users/user/Documents/hoge
plugins: cov-3.0.0
collected 2 items
tests/test_sample.py .F [100%]
======================================================================== FAILURES =========================================================================
_____________________________________________________________________ test_unexpected _____________________________________________________________________
def test_unexpected():
> assert add(3, 5) == 10
E assert 8 == 10
E + where 8 = add(3, 5)
tests/test_sample.py:16: AssertionError
================================================================= short test summary info =================================================================
FAILED tests/test_sample.py::test_unexpected - assert 8 == 10
=============================================================== 1 failed, 1 passed in 0.03s ===============================================================
テストが始まってすぐあたりの
tests/test_sample.py .F
という出力ですが、
今回のファイルにある2つのテストのうち、1つ目が成功(ピリオド「.」)、2つ目が失敗(Failed「F」)であることを表しています。
その後、失敗した2つ目のテストの詳細がAssertionError
として出力されています。
テスト前後で前処理・後処理を行う
テストの前後に処理を行いたい場合は「フィクスチャ」という機能を使うことができます。
-
「フィクスチャ」とは「テスト前後にpytestにより実行される関数」とのことでした(参考)。(汎用的な言葉ではなく、どちらかというと専らpytestの文脈で使用される言葉なのでしょうか...?)
-
関数を
@pytest.fixture
でアノテートすることで、その関数をフィクスチャとして使用することができます。 -
フィクスチャ関数における
yield
にて、テストが呼び出されます。 -
テストを行う関数の引数に、フィクスチャの関数を引数として渡すことで、フィクスチャとして使用します。
以下では、テストの前後にメッセージをprintする簡単な例を見てみます。
フィクスチャとして使用する関数decorate()
の yield
の部分に、テストを実行する関数 test_[un]expected()
が埋め込まれる、みたいなイメージです。
import pytest
# テストしたい関数
def add(a, b):
return a + b
# フィクスチャ
@pytest.fixture()
def decorate():
# 前処理
print('テストを始めます!')
# テスト実行
yield
# 後処理
print('テストを終わります!')
print('================')
# テスト(成功パターン)
def test_expected(decorate):
print('テストやってます!')
assert add(3, 5) == 8
# テスト(失敗パターン)
def test_unexpected(decorate):
print('テストやってます!')
assert add(3, 5) == 9
コマンドでテストを実行してみましょう。
テスト結果内にてprint()の結果を見るためには、オプションcaputure=no
を設定します。
$ pytest tests/test_sample.py --capture=no
テスト結果は以下のようになりました。
============================= test session starts ==============================
platform darwin -- Python 3.8.10, pytest-7.0.1, pluggy-1.0.0
rootdir: /Users/user/Documents/hoge
plugins: cov-3.0.0
collected 2 items
tests/test_sample.py テストを始めます!
テストやってます!
.テストを終わります!
================
テストを始めます!
テストやってます!
Fテストを終わります!
================
=================================== FAILURES ===================================
_______________________________ test_unexpected ________________________________
decorate = None
def test_unexpected(decorate):
print('テストやってます!')
> assert add(3, 5) == 9
E assert 8 == 9
E + where 8 = add(3, 5)
tests/test_sample.py:31: AssertionError
=========================== short test summary info ============================
FAILED tests/test_sample.py::test_unexpected - assert 8 == 9
========================= 1 failed, 1 passed in 0.02s ==========================
以下の部分がテストの概要ですね。
tests/test_sample.py テストを始めます!
テストやってます!
.テストを終わります!
================
テストを始めます!
テストやってます!
Fテストを終わります!
================
1回目のテストでは成功(「.」)し、2回目のテストでは失敗(「F」)しています。
テスト前後には「テストを始めます!」「テストを終わります!」とprintされています。
複数のテストに共通するリソースを作成する
このような場合も、フィクスチャを活用することができます。
ここでは、1~20のランダムな整数を返す関数 random_number()
をフィクスチャにして、複数のテスト関数にてテストします。(どんなテストやねんて感じですが、、)
1回目のテストは偶数であること、2回目のテストでは10以上であることを成功条件にしています。
今回フィクスチャを定義する際は
@pytest.fixture(scope='module')
というふうに記述し、フィクスチャのスコープを設定しています。
module
に設定すると、モジュール(=ソースコード)で一回だけフィクスチャが呼び出されることとなり、1回目のテストと2回目のテストで同じ値を渡すことができます。
スコープには以下を設定することができます。
スコープ | フィクスチャの実行タイミング |
---|---|
function | テストケース(テスト関数)ごとに実行(デフォルト) |
module | テストファイルで1回実行 |
class | テストクラスで1回実行 |
session | テスト全体で1回実行 |
import pytest
import random
# フィクスチャ
@pytest.fixture()
def random_number():
return random.randint(1, 20) # 1~20のランダムな整数を返す
# テスト(random_number()の戻り値が偶数の場合、成功)
def test1(random_number):
print('\n')
print('random_number: {}'.format(random_number))
assert random_number % 2 == 0
# テスト(random_number()の戻り値が10以上の場合、成功)
def test2(random_number):
print('\n')
print('random_number: {}'.format(random_number))
assert random_number >= 10
コマンドでテストを実行してみましょう。
テスト結果内にてprint()の結果を見るためには、オプションcaputure=no
を設定します。
$ pytest tests/test_sample.py --capture=no
テスト結果は以下のようになりました。
✘ 1
=================================================================== test session starts ===================================================================
platform darwin -- Python 3.8.10, pytest-7.0.1, pluggy-1.0.0
rootdir: /Users/user/Documents/hoge
plugins: cov-3.0.0
collected 2 items
tests/test_sample.py
random_number: 13
F
random_number: 13
.
======================================================================== FAILURES =========================================================================
__________________________________________________________________________ test1 __________________________________________________________________________
random_number = 13
def test1(random_number):
print('\n')
print('random_number: {}'.format(random_number))
> assert random_number % 2 == 0
E assert (13 % 2) == 0
tests/test_sample.py:15: AssertionError
================================================================= short test summary info =================================================================
FAILED tests/test_sample.py::test1 - assert (13 % 2) == 0
=============================================================== 1 failed, 1 passed in 0.03s ===============================================================
以下の部分が2回のテストの概要です。
random_number: 13
F
random_number: 13
.
今回フィクスチャのスコープを「module」(テストファイル)に設定したため、1回目も2回目も同じ13という値が渡っています。
偶数でないため1回目は失敗(「F])、10以上のため2回目は成功(「.」)しています。
複数パターンでテストする
ひとつのテスト関数で、複数パターンのテストをしたい場合は、引数を渡してあげます。
引数を渡すときは
@pytest.mark.parametrize('引数の名前', [引数の中身(複数)])
というデコレータを使えばできます。
以下では、aとbという引数にそれぞれ「2と3を渡すテスト」「1と7を渡すテスト」を行う例を行います。2つの引数の和が偶数であれば、成功としています。
import pytest
@pytest.mark.parametrize('a, b', [(2, 3), (1, 7)])
def test_add(a, b):
result = a + b
assert result % 2 == 0
pytest tests/test_sample.py
でテストを実行した結果は以下のようになりました。
============================= test session starts ==============================
platform darwin -- Python 3.8.10, pytest-7.0.1, pluggy-1.0.0
rootdir: /Users/user/Documents/hoge
plugins: cov-3.0.0
collected 2 items
tests/test_sample.py F. [100%]
=================================== FAILURES ===================================
________________________________ test_add[2-3] _________________________________
a = 2, b = 3
@pytest.mark.parametrize('a, b', [(2, 3), (1, 7)])
def test_add(a, b):
result = a + b
> assert result % 2 == 0
E assert (5 % 2) == 0
tests/test_sample.py:7: AssertionError
=========================== short test summary info ============================
FAILED tests/test_sample.py::test_add[2-3] - assert (5 % 2) == 0
========================= 1 failed, 1 passed in 0.02s ==========================
以下の出力から、1回目は失敗(2+3は偶数ではない)、2回目は成功(1+7は偶数である)していることがわかります。
tests/test_sample.py F.
まとめ
今回はpytestに入門してみました。
まだまだ他にも便利な使い方があるようなので(モックとか)、使うことがあったら記事にしてみます。