pytestでdifflibを使ってみた (例付き)

# 紹介
はじめてQiita記事を書きます、よろしくお願いします。

普段はECシステムを担当してます、自分のチームはテストをちゃんと書くのを決めたので、pytest導入をしようと思います。今回pytest導入したプロジェクトはメールテンプレートAPIです。ここのメールテンプレートAPIは、ECサイトで注文後、お客様にメールを届く文面生成処理のAPIです。

従来bashで実装されていたメール文面生成処理をPythonのAPI(Lambdaで実装)に移行した。

- python移行したテストにあたって以下のような課題があった
    1. レスポンス項目のうちの一つであるメール文面をアサーションする必要があったが、内容が長いためassertを使うと結果が見辛い
    2. メール文面は注文元ショップ(Yahoo, Amazon...など)とメール種類(注文確認、出荷...など)の組み合わせにより沢山の種類があり、全ての組み合わせに対してテストケースを用意するのは大変
- それぞれの課題について、以下の方法にて解決した
    1. メール文面のアサーションには、`git diff`コマンドの出力結果などにも用いられている標準ライブラリの[difflib](https://docs.python.org/ja/3.8/library/difflib.html)を使ったところ、見慣れた形式で差分比較ができた
    2. 複数データに対するテストケースの定義は、`pytest`のparametetrizeアノテーションを用いた

## pytestの標準test discoveryルール ([詳細](https://docs.pytest.org/en/latest/goodpractices.html#test-discovery))
- ファイル名は`test_*.py` や `*_test.py`
- クラス名は `Test` で始まる必要があり
- 関数、クラスのメソッドは先頭にプレフィックス`test_`つける必要があり





## Parametrizing
色々パラメーターをテストしたい時に、`@pytest.mark.parametrize`は便利です。
この例で、沢山のグループ`店舗、メールタイプ、注文番号`をテストしたいので、1個テスト関数を作ればOKです。


# 手順
```terminal
# pytestインストール
$ pip install pytest

# pytestインストールできた確認
$ pytest --version

# pytestを実行
# -vは結果を詳細に表示する
$ pytest -v

```


# 入力
```txt:1_1_2xxx.txt
xxx様

この度は「店舗」をご利用いただき、誠にありがとうございます。

ご注文内容は下記の通りです。test

商品発送後、改めて『商品の発送のお知らせ』メールを送信させていただきます。
```

# コード
```py3:test_message.py
from pathlib import Path
import os
import difflib
import pytest

local_path = Path.cwd()
txt_dir = local_path.joinpath('data')


def get_testdata():
    """
    `data`フォルダーにファイル名からテストデータを作る

    Returns:
        list: parametrizeのtestdataとids
    """

    testdata = []
    testdata_ids = []
    for filename_with_path in txt_dir.glob('*_*_*.txt'):
        filename = os.path.basename(filename_with_path)
        mail_type = filename.split('_')[0]
        order_shop = filename.split('_')[1]
        order_no = filename.split('_')[2]
        testdata.append(tuple((order_shop, mail_type, order_no)))
        testdata_ids.append(f'{order_shop}_{mail_type}_{order_no}')
    return [testdata, testdata_ids]


testdata = get_testdata()[0]
testdata_ids = get_testdata()[1]


@pytest.mark.parametrize('order_shop, mail_type, order_no',
                         testdata,
                         ids=testdata_ids)
def test_mail_message(order_shop: str, mail_type: str, order_no: str):
    """
    shell作ったメールテンプレートとpythonのを比較する

    Args:
        order_shop (str): 店舗
        mail_type (str): メールタイプ
        order_no (str): 注文番号
    """

    # このプログラミングで作成したメールテンプレートを読み込む
    fromfile = '''xxx様

この度は「店舗」をご利用いただき、誠にありがとうございます。

ご注文内容は下記の通りです。

商品発送後、改めて『商品の発送のお知らせ』メールを送信させていただきます。'''

    # shellで作成したメールテンプレートを読み込む
    compare_file = local_path.joinpath('data').joinpath(
        f'{order_shop}_{mail_type}_{order_no}')
    if compare_file.is_file():
        with open(compare_file) as f:
            tofile = f.read()

    # 比較を実施
    diff = difflib.unified_diff(fromfile.splitlines(keepends=True),
                                tofile.splitlines(keepends=True),
                                fromfile='python-mail-api',
                                tofile='shell-mail-api')
    diff_str = ''.join(diff)
    print(diff_str)

    assert not diff_str

```

# 結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483059/ed905a3e-516d-d0c5-4bc2-9f1037c97cc9.png)
メールの差分を見えます！