【Django】pytest-djangoでテストをもっと書きやすくする

Django

こんにちは、かつコーチです。

前回は標準のTestCaseによるテストの書き方を扱いました。
TestCaseはDjangoに標準で組み込まれている分、追加のライブラリなしで始められるのが魅力です。

一方で、テストの規模が大きくなってくると、クラスベースのunittestスタイルよりもpytestのシンプルな書き方を好むチームが増えてきます。
この記事ではpytest-djangoを使ったテストの書き方と、標準のTestCaseとの使い分けを、比較・ベストプラクティス中心に解説します。

TestCaseとpytest-djangoの違い

なぜpytest-djangoが選ばれるのか

pytest-djangoは、Djangoのpytestプラグインで、pytestが持つシンプルな関数ベースのテスト記法をDjangoプロジェクトに持ち込めるようにするものです。

観点標準のTestCase(unittest)pytest-django
テストの書き方クラス継承・self.assert*メソッド関数ベース・素のassert
データ準備setUpメソッドfixture@pytest.fixture
実行コマンドpython manage.py testpytest
パラメータ化テスト標準機能では手薄(subTestで代用)@pytest.mark.parametrizeで簡潔に書ける
プラグインエコシステムDjango標準機能のみpytest-covpytest-xdist(並列実行)など豊富
学習コストDjangoの知識だけで完結pytest独自の概念(fixture、conftest.py)の理解が必要

「追加のライブラリを増やしたくない」「チームがunittestスタイルに慣れている」ならTestCaseのままで十分です。
「アサーションをシンプルに書きたい」「パラメータ化テストや並列実行を活用したい」というチームにはpytest-djangoが向いています。

導入時の設定

pip install pytest pytest-django

プロジェクトルートにpytest.ini(またはpyproject.toml)を用意し、Djangoの設定モジュールの場所を教えます。

# pytest.ini
[pytest]

DJANGO_SETTINGS_MODULE = config.settings python_files = tests.py test_*.py *_tests.py

pytest-djangoでのテストの書き方

素の関数とassert文でテストを書く

TestCaseのクラス継承が不要になり、通常の関数として書けます。

# blog/tests/test_models.py
import pytest
from blog.models import Article


@pytest.mark.django_db
def test_str_returns_title():
    article = Article.objects.create(title="テスト記事", body="本文です")
    assert str(article) == "テスト記事"

ポイントは@pytest.mark.django_dbデコレータです。
これを付けないとデータベースへのアクセスが許可されず、Article.objects.create()の時点でエラーになります。
DBアクセスを伴うテストには必ず付け忘れないよう注意が必要です。

fixtureで共通の準備処理をまとめる

TestCasesetUpに相当する仕組みがfixtureです。

# blog/tests/conftest.py
import pytest
from blog.models import Article


@pytest.fixture
def published_article(db):
    return Article.objects.create(title="公開済み記事", is_published=True)
# blog/tests/test_views.py
import pytest
from django.urls import reverse


@pytest.mark.django_db
def test_article_list_shows_published_article(client, published_article):
    response = client.get(reverse("blog:article_list"))
    assert response.status_code == 200
    assert "公開済み記事" in response.content.decode()

conftest.pyに置いたfixtureは、同じディレクトリ以下のテストファイルから自動的に読み込まれます。
clientもpytest-djangoが用意している組み込みfixtureで、TestCaseself.clientと同じ役割を果たします。

parametrizeで同じロジックのテストを網羅する

複数の入力パターンを検証したいとき、pytestのパラメータ化テストが特に威力を発揮します。

@pytest.mark.django_db
@pytest.mark.parametrize(
    "title, expected_valid",
    [
        ("", False),
        ("a", True),
        ("a" * 200, False),  # タイトルの上限を超えている想定
    ],
)
def test_article_title_validation(title, expected_valid):
    article = Article(title=title)
    is_valid = article.is_title_valid()
    assert is_valid == expected_valid

TestCaseで同じことをやろうとすると、subTestを使うかテストメソッドを複数書くかの選択になり、どちらもやや冗長になりがちです。

よくあるつまずきポイント

❌ Before:django_dbマーカーの付け忘れでエラーになる

筆者がpytest-djangoを導入した際に最初にハマったのが、このエラーでした。

django.core.exceptions.ImproperlyConfigured: Database access not allowed, use the "django_db" fixture to enable it.

TestCaseではクラスを継承するだけで自動的にDBアクセスが許可されていたため、その感覚のまま関数ベースのテストを書き、マーカーを付け忘れていたのが原因でした。

✅ After:pytest.iniでマーカーの付け忘れを防ぐ工夫をする

マーカーの付け忘れを完全になくすことは難しいですが、DBアクセスが必要なテストファイルの単位で運用ルールを決めておくと事故が減ります。

# ✅ After:DBアクセスが必要な関数には必ずdjango_dbを付ける
import pytest


@pytest.mark.django_db
def test_article_creation():
    ...

また、CIでテストが通らなかった場合にこのエラーメッセージが出ていれば、原因はほぼ確実にdjango_dbマーカーの付け忘れだと即座に判断できるようになりました。
エラーメッセージ自体が親切なので、一度原因を理解しておけば以降は迷わず対処できます。

応用・一歩先の使い方

既存のTestCaseと共存させる移行戦略

pytest-djangoは、既存のTestCaseベースのテストも変更なしにそのまま実行できます。
つまり、プロジェクト全体を一気に書き換える必要はなく、新規に書くテストからpytestスタイルに寄せていく、という段階的な移行が可能です。

# 既存のTestCaseベースのテストもpytest経由で実行できる
pytest blog/tests/

pytest-covとpytest-xdistで開発体験を上げる

pytest-djangoのエコシステムを活かすなら、以下のプラグインも合わせて検討する価値があります。

pip install pytest-cov pytest-xdist
pytest --cov=blog -n auto

--covでカバレッジを計測し、-n autoでCPUコア数に応じてテストを並列実行できます。
テストの数が増えてきたプロジェクトほど、この並列実行の恩恵は大きくなります。

まとめ

この記事のポイント

  • pytest-djangoは関数ベースの記法とfixtureで、TestCaseよりシンプルにテストを書ける
  • DBアクセスを伴うテストには@pytest.mark.django_dbを忘れずに付ける
  • parametrizeで同じロジックの複数パターン検証を簡潔に書ける
  • 既存のTestCaseと共存できるため、段階的な移行が可能
  • pytest-covpytest-xdistなどのプラグインでカバレッジ計測や並列実行を強化できる

次に読むべき記事

テストの書き方を固めたら、次は本番環境へのデプロイに進みましょう。
「DjangoアプリをRenderにデプロイする方法」の記事で、実際のデプロイ手順を解説しています。

タグ: Django, 上級者向け, テスト

タイトルとURLをコピーしました