pytestを使ってみる 

<h1>概要

今回はpytestを使ってUnitTestをやってみます。

最低限の使い方のみ記します。
フィクスチャを使ったファイル入出力、モックを使った複数クラスのテストについては次回以降書きます。

それと、pipenvのinstallで一瞬キョドってしまったので
回避方法を簡単に記載しておきます。

<h2>テスト対象のコード(呼び出し先)

西暦が閏年かそうでないかをbool値で返すコードをテストします。

```check_leaptime.py
def is_leaptime(n: int) -> bool:
    if n % 4 == 0:
        if (n % 100) == 0 and (n % 400) != 0:
            return False
    return True

```
ここで使用している関数のアノテーションは
引数や返り値の型を指定＆可視化する為に記載しています。


<h2>メインモジュール(呼び出し元)

テストしたいコードを呼び出します。

```test_check_leaptime.py

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に引数を渡した結果をデバッグさせてみます。

それでは動かしてみましょう..

<h3>仮想環境に接続

そもそも仮想環境を用意していなかったので作成します。
まず``pip install pipenv``します。
おおっと、WARNINGがでた。:sob:

```python:cmd
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ファイル``で実行です。

```python:CMD
(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としてみます。

```python:CMD
(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となった箇所から下の行について評価が行われません。

<h3>Errorとなる行も含めてすべてのテストを行う


そこでデコレータを使い、以下の用に定義します。

```python:test_check_leaptime.py
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を拾えるか確認してみます。

```python:CMD
(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 =============================================
```

期待通り、すべてのテストを流すことが出来ました。


<h1>参考にさせて頂いたサイト:bow:

https://rinatz.github.io/python-book/ch08-02-pytest/
https://gammasoft.jp/blog/python3-function-annotations/