pytest使い方まとめ


今まで「テストコード」をほとんど書いたことがなかったのですが、pythonのテストコードを書く機会があり、慌てて使い方を学びました。本記事ではpython単体テストのフレームワークであるpytestの使い方を要点を絞ってまとめました。

参考:公式ドキュメント(https://docs.pytest.org/en/latest/contents.html)


pytestとは

pythonでの単体テストを行うためのフレームワーク。同様なものでunittestというものがあるが、pytestの方が人気らしい。
下記がpytestの特徴

  • テスト失敗時の情報が詳細
  • テスト対象のモジュール、関数を自動で発見
  • フィクスチャ機能を利用することで、モックなど、テストの前処理ができる。(後述)
  • unittestにも準拠している

インストール

pip install -U pytest

簡単な使用方法

単純な単体テスト

基本的に、下記の要領でテストを実行できる。

  • テストケースをassert <条件式> で記載する
  • pytestを実行する

ファイル名, メソッド名をtest_*で記載すると、自動でテストコードを発見してくれる(自分で指定することも可能)

test_sample.py
def func(x):
    return x + 1

def test_answer():
    assert func(3) == 5

上記test_sample.pyファイルを作成し、
pytestを実行。

すると、下記のようなテスト結果が表示される。

テスト結果
========================================== test session starts ===========================================
platform darwin -- Python 3.7.6, pytest-5.4.3, py-1.8.1, pluggy-0.13.1
rootdir: /Users/xin/work/study/pytest
plugins: hypothesis-5.5.4, arraydiff-0.3, remotedata-0.3.2, openfiles-0.4.0, doctestplus-0.5.0, astropy-header-0.1.2
collected 1 item                                                                                         

test_sample.py F                                                                                   [100%]

================================================ FAILURES ================================================
______________________________________________ test_answer _______________________________________________

    def test_answer():
>       assert func(3) == 5
E       assert 4 == 5
E        +  where 4 = func(3)

test_sample.py:5: AssertionError
======================================== short test summary info =========================================
FAILED test_sample.py::test_answer - assert 4 == 5
=========================================== 1 failed in 0.18s ============================================

例外処理のテスト

pytest.raisesにより、例外のテストが可能

error_sample.py
import pytest

def f():
    raise SystemExit(1)

def test_mytest():
    with pytest.raises(SystemExit):
        f()

クラス内の複数のテストの実行

Testをprefixとしたクラスを作成することで、内部のテストメソッドを同時にテストできる。

test_class.py
class TestClass:
    def test_one(self):
        x = "this"
        assert "h" in x

    def test_two(self):
        x = "hello"
        assert hasattr(x, "check")

フィクスチャ

フィクスチャとは

テストの実行環境をセットアップするために用意されたツール。
いくつか便利そうなものをピックアップします。
詳細は下記を参照(https://docs.pytest.org/en/latest/fixture.html)

tmpdir

tmpdirフィクスチャにより、テスト時に一時的に利用できる固有のディレクトリを作成できる。

test_tempdir.py
import os

def test_create_file(tmpdir):
    p = tmpdir.mkdir("sub").join("hello.txt") # 一時ディレクトリの作成
    p.write("content")
    assert p.read() == "content"
    assert len(tmpdir.listdir()) == 1
    assert 0

monkeypatch

monkeypatchフィクスチャにより、オブジェクトのモックを作成できる。
例えば下記のようなプロダクトコードがあり、仮に一get_value()が容易に実行できない関数だとする。

monkeypatch_product.py
def return_value(): # プロダクトコード
    a = get_value()
    return a

def get_value(): # 容易に実行できない関数
    return 1

この時、monkeypatch.setattrによりget_value()を自作のモック関数に差し替えてテストを実行することができる。

monkeypatch_test.py
import monkeypatch_product

# テストコード
def test_return_value(monkeypatch):
    def mock_get_value():
        return 100

    # モック関数に差し替え
    monkeypatch.setattr(monkeypatch_product, "get_value", mock_get_value)
    res = monkeypatch_product.return_value()
    assert res == 100

また、monkeypatch.setenvで環境変数のモック化も可能。

monkeypatch_env.py
import os

# プロダクトコード
def get_os_user_lower():
    username = os.getenv("USER")

    if username is None:
        raise OSError("USER environment is not set.")

    return username.lower()

# テストコード
def test_get_os_user_lower(monkeypatch):
    monkeypatch.setenv("USER", "TestingUser")
    assert get_os_user_lower() == "testinguser"