ユニットテストしたい(pytest

$ pytest --version
pytest 8.3.3

$ pytest
$ pytest --verbose
$ pytest ファイル名

pytestはPythonのユニットテスト群をまとめて実行できるツールです。 プロジェクトのルートディレクトリで実行すればテストをまとめて実行できます。 --verboseオプションで、それぞれのテストごとに結果を表示できます。

インストールしたい(pytest

  • pipxでインストール

$ pipx install pytest
  • poetryでインストール

$ poetry add pytest --group=test
$ poetry add pytest-mock --group=test  # モックを使ったユニットテスト
$ poetry add pytest-cov --group=test   # カバレッジの計測
$ poetry add pytest-html --group=test  # テスト結果をHTMLファイルに出力

poetryで管理している場合は--group=testに分類するとよいと思います。

  • uvでインストール

$ uv tool install pytest
$ uv tool install pytest-mock
$ uv tool install pytest-cov

pipxuvを使ってシステム(の仮想環境)にインストールできます。

テスト結果をHTMLファイルに出力する場合はpytest-htmlのが必要です。 unittest.mockを使う場合は、 pytest-mockもインストールしておくとよいです。 カバレッジを計測した場合はpytest-covが必要です。

ディレクトリ構造

$ cd プロジェクト
$ tree
.
├── 自作パッケージ名
│   ├── __init__.py
│   ├── 自作モジュール1.py
│   ├── 自作モジュール2.py
├── tests
│   ├── __init__.py
│   ├── test_自作モジュール1.py
│   ├── test_自作モジュール2.py
├── poetry.toml
├── pyproject.toml

ユニットテスト用のファイルは、testsディレクトリの中に作成します。 ファイル名の先頭はtest_にします。

上のサンプルはpoetryで作成したディレクトリ構造です。 自作パッケージと同階層にtestsディレクトリを作成し、 その中にユニットテストを作成しています。

 1import pytest
 2from unittest.mock import patch
 3
 4@patch("subprocess.run")
 5def test_download(mock_subprocess_run):
 6
 7    """Test download method"""
 8
 9    # テスト用URL
10    url = TEST_SHARED_URL
11    sheet = Sheet(
12        url=url,
13        filename="output.csv")
14    sheet.download()
15
16    mock_subprocess_run.assert_called_with(
17        ["wget", "--quiet", "-O", "output.csv", sheet.export_url]
18    )

上のサンプルは、 sheet.downloadの中で、 subprocess.runを使って wgetを呼んでいる場合のテストです。

subprocess.runをモックすることで、wgetを実行せずにテストできるようにしています。 テスト関数の引数名はモック名にします。 この場合はmock_subprocess_runでアクセスできるようになります。

wgetを実行していないため、filename="output.csv"に設定したファイルは作成されません。 そのため、assert_called_withを使って、指定した引数で関数が呼ばれたかどうかで、動作確認しています。

モックしたい

モック/パッチの作り方はまだわかっていないので、 ChatGPTに聞きながら書くことが多いです。

ファイル書き込みをモックしたい(pathlib.Path.write_text

1def 関数名(引数):
2    p = Path("ファイル名")
3    p.write_text("ファイルの内容", encoding="utf-8")

pathlib.Path.write_textを使っている関数のユニットテストを作成したときのサンプルです。 関数名や引数名は適当に置き換えて読んでください。

 1from unittest.mock import patch
 2
 3@path("pathlib.Path.write_text")
 4def test_関数名(mock_write):
 5    # test strings
 6    text = "ファイル内容"
 7
 8    # run a function
 9    関数名(引数)
10
11    # assertion
12    # write_textが1回だけ呼ばれたことを確認
13    mock_write.assert_called_once_with(text, encoding="utf-8")

pathlib.Path.write_textをモックします。 write_textは内部でpathlib.Path.openを使っていますが、 mock_openは必要ありません。

注釈

open関数を使う場合はmock_openが必要です。

例外をテストしたい(pytest.raises

1import pytest
2
3def test_関数名():
4    with pytest.raise(例外名):
5        関数(...)  # <- 例外を発生させる

pytest.raiseで例外をテストできます。s

繰り返しテストしたい(@pytest.mark.parametrize

1@pytest.mark.parametrize(
2    "a, b, expected",
3    [ (1, 2, 3),
4      (3, 4, 5),]
5)
6def test_関数名(a, b, expected):
7    assert 関数名(a, b) == expected

@pytest.mark.parametrizeデコレータで、 異なる値で繰り返しテストできます。

テスト用の設定したい(@pytest.fixture

1@pytext.fixture
2def sample_data():
3    return [1, 2, 3]
4
5def test_data_length(sample_data):
6    assert len(sample_data) == 3

@pytest.fixtureでテスト用の設定値を作成できます。