Pythonでユニットテスト書いてみよう－3

##概要
Pythonのユニットテストについて以下の２つ標準機能について紹介しました。
1. [unittest](https://qiita.com/_dakc_/items/e663359c81645bc4e65e)
2. [doctest](https://qiita.com/_dakc_/items/6330bd56bdaae80c1cca)

今回はpytestフレームワークを紹介します。

## pytest
[公式サイト](https://docs.pytest.org/en/latest/getting-started.htm)には以下の様に書いてあります。
>
pytest is a framework that makes building simple and scalable tests easy. Tests are expressive and readable—no boilerplate code required. Get started in minutes with a small unit test or complex functional test for your application or library.

要約すると、
①シンプルでスケーラブルなテストフレームワークだよ
②読みやすいよ
③小規模なテストから複雑なテストもできるよ


#### インストール
unittestとdoctestと違って、Pythonインストールした時点では入ってこないため、**別当インストールする必要**があります。以下のコマンドでインストールができます。

```bash
pip install pytest
```
正常にインストール出来たら、バージョンを確認すると以下のように表示されます。

```bash
# pytest --version
This is pytest version 5.2.1, imported from /usr/local/lib/python3.6/site-packages/pytest.py
```


#### 書き方
pytestの書き方はとても簡単です。テスト用の関数を用意し、その中にテストケースを記述するのみです。

```python
# テスト対象の関数
def func_XXX():
    処理の記述

# 1. テスト用の関数の作成
def test_func_XXX():
    # 2. テストケースを記入
    assert func_XXX(2) == 6
```


#### Let's Code
では、実際にテストを実行してみましょう。
まずは、ワークフォルダーを作成し、その中にtest_sample1.pyを作成してください。「test_sample1.py」の中身を以下にしてください。

```test_sample1.py
def add(num1, num2):
    """
    引数として受けた数値を足し算し、
    その結果を返す。
    """
    return num1 + num2

def test_add():
    """
    2 + 3 = 6
    """
    assert add(2,3) == 6
```

ターミナルから上記で作ったワークフォルダーに移動し、pytestを入力し、ENTERキーを押してください。以下のようにテストが失敗したメッセージが表示されます。

```bash
# pytest
===================================== test session starts =====================================
platform linux -- Python 3.6.9, pytest-5.2.1, py-1.8.0, pluggy-0.13.0
rootdir: /home/dakc
collected 1 item

test_sample1.py F                                                                       [100%]

========================================== FAILURES ===========================================
__________________________________________ test_add ___________________________________________

    def test_add():
        """
        2 + 3 = 6
        """
>       assert add(2,3) == 6
E       assert 5 == 6
E        +  where 5 = add(2, 3)

test_sample1.py:12: AssertionError
====================================== 1 failed in 0.02s ======================================
#
```
pytestを入力するだけでテストが実行されますが、**ファイルの名前**が影響しています。例えば、上記のファイルの名前を「sample.py」に変えれば、テストが実行されません。

```bash
# pytest
===================================== test session starts =====================================
platform linux -- Python 3.6.9, pytest-5.2.1, py-1.8.0, pluggy-0.13.0
rootdir: /home/dakc
collected 0 items

==================================== no tests ran in 0.01s ====================================
```
テストを実行するには以下の注意を払う必要がある。
1. ファイル名をtest_XXXにする
2. ファイル名をXXX_testにする
3. 明示的にファイルを指定する

上記の例のtest_sample1.pyは、「1.」のtest_XXXに該当します。
また、sample.pyの場合はエラーになりましたが、「3.」のように**pytest sample.py**を実行すれば、テストが実行されます。


#### まとめてテスト
複数テストファイルをまとめてテストしたいケースがあります。その場合の手順を紹介します。
先ほど作ったワークフォルダーに「test_sample2.py」を作成し、以下の内容にしてください。

```test_sample2.py
def add(num1, num2):
    """
    引数として受けた数値を足し算し、
    その結果を返す。
    """
    return num1 + num2

def test_add():
    """
    2 + 3 = 5
    """
    assert add(2,3) == 5
```
（違いは今回の**テストケースが正しい**内容「2+3＝5」になっただけです。）
そして、pytestを実行してください。

```bash
# pytest
===================================== test session starts =====================================
platform linux -- Python 3.6.9, pytest-5.2.1, py-1.8.0, pluggy-0.13.0
rootdir: /home/dakc
collected 2 items

test_sample1.py F                                                                       [ 50%]
test_sample2.py .                                                                       [100%]

========================================== FAILURES ===========================================
__________________________________________ test_add ___________________________________________

    def test_add():
        """
        2 + 3 = 6
        """
>       assert add(2,3) == 6
E       assert 5 == 6
E        +  where 5 = add(2, 3)

test_sample1.py:12: AssertionError
================================= 1 failed, 1 passed in 0.16s =================================
#
```
出力内容通り、2つのテストケースの中で一つが失敗し、一つが成功した事が分かります。
test_sample1.pyの12行のテストが失敗した事がわかります。

一回の実行で、**カレントディレクトリのすべてのファイル**を一斉にテストしてくれます。

## 補足-1
unittestとdoctestと同様に、ターミナルから**python -m pytest ファイル名**を実行する事でユニットテストができます。test_XXXまたは、XXX_testにファイル名を揃えていれば、最後のファイル名省略することができます。

## 補足-2
pytestにはたくさんの便利な機能ありますが、その中の一つ「mark」について紹介します。たくさんのテストケースの中から特定の分だけテストしたい時に大変役に立つ機能です。関数に**@pytest.mark.マーク名**デコレートする事でこの機能を利用できます。

```sample.py

import pytest
def add(num1, num2):
    """
    引数として受けた数値を足し算し、
    その結果を返す。
    """
    return num1 + num2

@pytest.mark.success
def test_add_success():
    """
    2 + 3 = 5
    """
    assert add(2,3) == 5

@pytest.mark.failure
def test_add_failure():
    """
    2 + 3 = 6
    """
    assert add(2,3) == 6
```
上記の例では、「add」関数の成功と失敗のテストケースを記述した関数が2つあります。成功するテストケースを書いた関数に「success」マークをつけました。そして、失敗するテストケースの関数には「failure」マークをつけました。

例えば、「add」の中身の一部を変える必要があり、その影響が「test_add_success」のみにあるとします。そのために、「test_add_success」関数だけテストしたい場面が出てきたとします。このような時はpytestのマーク機能を使います。
以下のコマンドで特定のマークのみテストします。
pytest ファイル名 **-m マーク名**

```bash

# pytest sample.py -m success
===================================== test session starts =====================================
platform linux -- Python 3.6.9, pytest-5.2.1, py-1.8.0, pluggy-0.13.0
rootdir: /home/dakc
collected 2 items / 1 deselected / 1 selected

sample.py .                                                                             [100%]

====================================== warnings summary =======================================
/usr/local/lib/python3.6/site-packages/_pytest/mark/structures.py:325
  /usr/local/lib/python3.6/site-packages/_pytest/mark/structures.py:325: PytestUnknownMarkWarning: Unknown pytest.mark.success - is this a typo?  You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/latest/mark.html
    PytestUnknownMarkWarning,

/usr/local/lib/python3.6/site-packages/_pytest/mark/structures.py:325
  /usr/local/lib/python3.6/site-packages/_pytest/mark/structures.py:325: PytestUnknownMarkWarning: Unknown pytest.mark.failure - is this a typo?  You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/latest/mark.html
    PytestUnknownMarkWarning,

-- Docs: https://docs.pytest.org/en/latest/warnings.html
========================= 1 passed, 1 deselected, 2 warnings in 0.13s =========================
```

## 最後に
Pythonのユニットテストについて３回に分けて三つの方法を使って紹介しました。
1. [第１回目:unittestについて](https://qiita.com/_dakc_/items/e663359c81645bc4e65e)
2. [第２回目:doctestについて](https://qiita.com/_dakc_/items/6330bd56bdaae80c1cca)
3. [第３回目:pytestについて](./)
何かの参考になれば幸いです。