pytestメモ

これは?

単体テストやりたいよね〜 pytestの書き方メモっとこうか

pytestの準備

インストール

pip install pytest

toyコード作成

ディレクトリ構成

pytest_work/
  data/
    out.csv
  src/
    work.py
  tests/
    test_sample.py

work.py

# -*- coding: utf-8 -*-
"""
DFを二つ作り、結合してその件数をチェックする処理をpytestで実施してみる。

"""
import pandas as pd
from pathlib import Path

def main():
    a = pd.DataFrame(
        {
            'a': [1, 2, 3],
            'b': [12, 13, 14]
         }
    )
    b = pd.DataFrame(
        {
            'a': [3, 4, 5],
            'c': ['a', 'b', 'c']
        }
    )
    c = pd.merge(a, b, on='a')
    out_file = Path.cwd().parent / "data" / "out.csv"
    c.to_csv(out_file, index=False)


if __name__ == '__main__':
    main()

work.pyを実行すると、DFのaとbの'a'というカラムの重複分がDFとして作成されて、data/out.csvとして出力される。
これに対し、tests/test_sample.pyでpytestを実施するイメージでやってみる。

テスト

test_sample.py

# -*- coding: utf-8 -*-
import pytest
import pandas as pd
from pathlib import Path


class TestSample:
    def test_return(self):
        x = 'test'
        assert x.find('st') != -1

    def test_uniq(self):
        target_file = Path.cwd().parent / "data" / "out.csv"
        x = pd.read_csv(target_file)
        assert len(x) != 0


if __name__ == '__main__':
    a = TestSample()
    a.test_uniq()

classにTestから始まる名称を作成し、その中の関数にtest_で始まるように定義していく。
test_return関数は、固定の変数に対するテストでtest_uniqがout.csvが0件でなければエラーとなるように記載している。

この状態で、tests/に移動して以下を実行するとテストができる。

(pytest-work-P7cVJe5b-py3.9) bash-3.2$ pytest test_sample.py
========================================================== test session starts ==========================================================
platform darwin -- Python 3.9.5, pytest-7.2.1, pluggy-1.0.0
rootdir: /Users/shirai_y/work/tmp/pytest_work/tests
collected 2 items

test_sample.py ..                                                                                                                 [100%]

=========================================================== 2 passed in 0.57s ===========================================================

これは、2つのテストをpassしたよってこと。

特定のテストだけ走らせたい場合は以下のようにすると良い。

(pytest-work-P7cVJe5b-py3.9) bash-3.2$ pytest test_sample.py::TestSample::test_uniq
========================================================== test session starts ==========================================================
platform darwin -- Python 3.9.5, pytest-7.2.1, pluggy-1.0.0
rootdir: /Users/shirai_y/work/tmp/pytest_work/tests
collected 1 item

test_sample.py .                                                                                                                  [100%]

=========================================================== 1 passed in 0.54s ===========================================================

テストファイル名の後に「::」でクラス名やその後に「::」で関数名を記載すると、指定したテストだけが実施できる。

便利な機能

assertの第二引数にメッセージを書くと、エラーの内容がテスト時に出力される。

test_sample.py

# -*- coding: utf-8 -*-
import pytest
import pandas as pd
from pathlib import Path


class TestSample:
    def test_return(self):
        x = 'test'
        assert x.find('st') != -1

    def test_uniq(self):
        target_file = Path.cwd().parent / "data" / "out.csv"
        x = pd.read_csv(target_file)
        assert len(x) != 0, "重複しているレコードが無いよ!"  # こんな感じでエラーの内容を記載。


if __name__ == '__main__':
    a = TestSample()
    a.test_uniq()

pytest実行。

(pytest-work-P7cVJe5b-py3.9) bash-3.2$ pytest test_sample.py
========================================================== test session starts ==========================================================
platform darwin -- Python 3.9.5, pytest-7.2.1, pluggy-1.0.0
rootdir: /Users/shirai_y/work/tmp/pytest_work/tests
collected 2 items

test_sample.py .F                                                                                                                 [100%]

=============================================================== FAILURES ================================================================
_________________________________________________________ TestSample.test_uniq __________________________________________________________

self = <test_sample.TestSample object at 0x11c90f7f0>

    def test_uniq(self):
        target_file = Path.cwd().parent / "data" / "out.csv"
        x = pd.read_csv(target_file)
>       assert len(x) != 0, "重複しているレコードが無いよ!"
E       AssertionError: 重複しているレコードが無いよ!
E       assert 0 != 0
E        +  where 0 = len(Empty DataFrame\nColumns: [a, b, c]\nIndex: [])

test_sample.py:15: AssertionError
======================================================== short test summary info ========================================================
FAILED test_sample.py::TestSample::test_uniq - AssertionError: 重複しているレコードが無いよ!
====================================================== 1 failed, 1 passed in 0.42s ======================================================

エラーが出たときに何のエラーなのかがわかりやすくて良き。

fixture

conftest.pyにテストで共通で使うような関数を記述しておくと、これをpytestの実体コード上で扱うことができる。

fixture.py

# -*- coding: utf-8 -*-
import pytest
import pandas as pd
from pathlib import Path


@pytest.fixture(scope="class")
def df_load_output():
    return pd.read_csv(Path.cwd().parent / "data" / "out.csv")

test_sample.py

# -*- coding: utf-8 -*-
import pytest


class TestSample:
    def test_return(self):
        x = 'test'
        assert x.find('st') != -1

    def test_uniq(self, df_load_output):  # 引数にtextureで指定した関数名を追加して、
        x = df_load_output  # その名称で使うとfixtureの処理が実行できる。
        assert len(x) != 0, "重複しているレコードが無いよ!"


if __name__ == '__main__':
    a = TestSample()
    a.test_uniq()

重複チェック以外にも同じファイルに対する処理をする場合に、毎回load処理を記述しなくても良いのが良いね。
あとは、DBコネクションとか共通系の処理はfixtureに書いてあげると良いみたい。