概要
今回はpytestを使ってUnitTestをやってみます。
最低限の使い方のみ記します。
フィクスチャを使ったファイル入出力、モックを使った複数クラスのテストについては次回以降書きます。
それと、pipenvのinstallで一瞬キョドってしまったので
回避方法を簡単に記載しておきます。
テスト対象のコード(呼び出し先)
西暦が閏年かそうでないかをbool値で返すコードをテストします。
def is_leaptime(n: int) -> bool:
if n % 4 == 0:
if (n % 100) == 0 and (n % 400) != 0:
return False
return True
ここで使用している関数のアノテーションは
引数や返り値の型を指定&可視化する為に記載しています。
メインモジュール(呼び出し元)
テストしたいコードを呼び出します。
import pytest
from is_leaptime import is_leaptime
# pytestは、test_で始まる関数を単体テスト対象として扱う
def test_is_leaptime():
assert is_leaptime(2020)
assert is_leaptime(2024)
assert not is_leaptime(2100)
assert not is_leaptime(2200)
行頭でimportしたpytestライブラリによって、
test_で始まる関数を単体テスト対象として扱うことが出来ます。
test_is_leaptime()内でassertを使用して、is_leaptimeに引数を渡した結果をデバッグさせてみます。
それでは動かしてみましょう..
仮想環境に接続
そもそも仮想環境を用意していなかったので作成します。
まずpip install pipenvします。
おおっと、WARNINGがでた。![]()
pip install pipenv
WARNING: The script virtualenv-clone.exe is installed in 'C:\Users\xxxx\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.8_qbz5n2kfra8p0\LocalCache\local-packages\Python38\Scripts' which is not on PATH.
Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.
WARNING: The script virtualenv.exe is installed in 'C:\Users\xxxx\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.8_qbz5n2kfra8p0\LocalCache\local-packages\Python38\Scripts' which is not on PATH.
Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.
WARNING: The scripts pipenv-resolver.exe and pipenv.exe are installed in 'C:\Users\xxxx\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.8_qbz5n2kfra8p0\LocalCache\local-packages\Python38\Scripts' which is not on PATH.
Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.
'C:~'のパスをそれぞれPATHに追加しろ、とのことでした。
コマンドプロンプトでPATHを追加→再実施してinstallできました。
続いて、仮想環境に繋ぐにはpipenv shellを叩きます。
接続したら早速テストを動かしてみます。
pytest pyファイルで実行です。
(pytest_practice-Qnr64jXF) C:\Users\xxxx\Desktop\work\python\pytest_practice>pytest test_check_leaptime.py
================================================= test session starts =================================================
platform win32 -- Python 3.6.8, pytest-6.2.2, py-1.10.0, pluggy-0.13.1
rootdir: C:\Users\user\Desktop\work\python\pytest_practice
collected 1 item
test_check_leaptime.py . [100%]
================================================== 1 passed in 0.01s ==================================================
無事にpassしたようです。
今度はcheck_leaptime.pyをいじってerrorを出力させてみます。2020の期待値をFalseとしてみます。
(pytest_practice-Qnr64jXF) C:\Users\user\Desktop\work\python\pytest_practice>pytest test_check_leaptime.py
================================================= test session starts =================================================
platform win32 -- Python 3.6.8, pytest-6.2.2, py-1.10.0, pluggy-0.13.1
rootdir: C:\Users\xxxx\Desktop\work\python\pytest_practice
collected 1 item
test_check_leaptime.py F [100%]
====================================================== FAILURES =======================================================
____________________________________________________ test_is_leaptime ____________________________________________________
def test_is_leaptime():
# ↓ 2020から2100に変更
> assert is_leaptime(2100)
E assert False
E + where False = is_leaptime(2100)
test_check_leaptime.py:6: AssertionError
=============================================== short test summary info ===============================================
FAILED test_check_leaptime.py::test_is_leaptime - assert False
================================================== 1 failed in 0.04s ==================================================
コードの出力結果とassertに書いた期待値が異なっている事を拾えています。
しかし、この書き方だとErrorとなった箇所から下の行について評価が行われません。
Errorとなる行も含めてすべてのテストを行う
そこでデコレータを使い、以下の用に定義します。
import pytest
from is_leaptime import is_leaptime
@pytest.mark.parametrize(('number', 'expected'), [
(2100, True),
(2024, True),
(2020, False),
(2200, False),
])
def test_is_leaptime(number, expected):
assert is_leaptime(number) == expected
デコレータに引数(number,expected)を定義し、西暦(number)を渡したとき、返される期待値(expected)を書きました。
1行目、3行目に(2100, True)・(2020, False)と書いて、errorを拾えるか確認してみます。
(pytest_practice-Qnr64jXF) C:\Users\xxxx\Desktop\work\python\pytest_practice>pytest test_check_leaptime.py
================================================= test session starts =================================================
platform win32 -- Python 3.6.8, pytest-6.2.2, py-1.10.0, pluggy-0.13.1
rootdir: C:\Users\xxxx\Desktop\work\python\pytest_practice
collected 4 items
test_check_leaptime.py F.F. [100%]
====================================================== FAILURES =======================================================
______________________________________________ test_is_leaptime[2100-True] _______________________________________________
number = 2100, expected = True
@pytest.mark.parametrize(('number', 'expected'), [
(2100, True),
(2024, True),
(2020, False),
(2200, False),
])
def test_is_leaptime(number, expected):
> assert is_leaptime(number) == expected
E assert False == True
E + where False = is_leaptime(2100)
test_check_leaptime.py:13: AssertionError
______________________________________________ test_is_leaptime[2020-False] ______________________________________________
number = 2020, expected = False
@pytest.mark.parametrize(('number', 'expected'), [
(2100, True),
(2024, True),
(2020, False),
(2200, False),
])
def test_is_leaptime(number, expected):
> assert is_leaptime(number) == expected
E assert True == False
E + where True = is_leaptime(2020)
test_check_leaptime.py:13: AssertionError
=============================================== short test summary info ===============================================
FAILED test_check_leaptime.py::test_is_leaptime[2100-True] - assert False == True
FAILED test_check_leaptime.py::test_is_leaptime[2020-False] - assert True == False
============================================= 2 failed, 2 passed in 0.11s =============================================
期待通り、すべてのテストを流すことが出来ました。
参考にさせて頂いたサイト
https://rinatz.github.io/python-book/ch08-02-pytest/
https://gammasoft.jp/blog/python3-function-annotations/