pytestでdjangoのmodelsをテストする方法をよく忘れるなあ、と思ってます。
新規でコードを書くより、バグ調査したり、レビューしてることが多くなると、いざ書こうとした時に「さて、どうやるんだっけな」的な感じになる。
というわけで書いときます。
- まず、pytestでdjangoをテストする話
- djangoのmodelsをテストする
- pytestでDjangoのViewをテストする
- 引数が渡ってくる仕組み
- pytest-djangoの仕組み
- 終わり
まず、pytestでdjangoをテストする話
とりあえず、前にpytestとdjangoの話について以下に書きました。
でも、上記ではdjangoのカバレッジを図っただけで、pytestからmodelsとかviewをテストする方法を書いてなかったです。
とりあえず、djangoをpytestするにはpytest-djangoを入れる必要があります。
入れた後は、pytest.iniあたりに以下の記載をするとdjangoをpytestでテストできます。(たぶん)
DJANGO_SETTINGS_MODULE = project.settings_test
最新情報はドキュメントを見てください。
djangoのmodelsをテストする
pytestからdjangoのmodelsをテストするときは、 pytest.mark.django_db
を使います。
例えば、こういうmodels app/models.py
の MyAppModel
があったとして、
from django.db import models class MyAppModel(models.Model): title = models.CharField(max_length=20) content = models.CharField(max_length=255, default="") @property def is_empty(self): return not bool(self.content)
こんな風にテストできます。
import pytest from app.models import MyAppModel @pytest.mark.django_db def test_app_is_empty(): mymodel = MyAppModel(title="sample model") assert mymodel.is_empty @pytest.mark.django_db def test_app_isnot_emmpty(): mymodel = MyAppModel(title="sample model", content="test") assert not mymodel.is_empty
このpytestのコードを見せると、「いや、もっと綺麗な書き方がある」「サンプルが微妙」とかツッコミたい人がいるかもしれませんが、とりあえずおいといてください。
pytest.mark.django_db
というデコデータをつけるとそのテストで、Djangoのテスト用DBが使えるようになります。
Django helpers — pytest-django documentation
pytestでDjangoのViewをテストする
テスト対象の準備
とりあえずテスト対象として、urlsがこんな感じ。
from django.urls import path from app import views as app_views urlpatterns = [ path('^', app_views.index_view), ]
app/views.py
がこんな感じ。
from django.http import HttpResponse def index_view(request): return HttpResponse("Hello World")
テストする
テストは二つの方法がある。djangoのTestClientを使う場合と、djangoのRequestFactoryを使う場合。基本的にpytest-djangoのドキュメントに書いてある話と同じだけど、改めて説明すると以下。
djangoのTestClientを使う場合はこんな感じ。
def test_app_view(client): response = client.get('/') assert response.status_code == 200
djangoのRequestFactoryを使う場合はこんな感じ。
def test_app_view(rf): request = rf.get('/') response = index_view(request) assert response.status_code == 200
djangoのDBを使わない場合は pytest.mark.django_db
というデコデータは不要。
ちなみに、TestClientなのかRequestFactoryなのかは、test関数の引数が何か、によって判定されてる。
client
という引数が渡ってくる想定で書くとDjangoのTestClientが渡ってくるし、rf
という引数が渡ってくる想定で書くとDjangoのRequestFactoryが渡ってくる。
引数が渡ってくる仕組み
最初に見たとき、test関数に渡ってくるclientとかrfがよくわからなかった。なんで引数渡ってくるんだろ、とか思ってた。
正直、pytestがよくわかってなくて雰囲気で触ってたので、それが原因だった。
簡単にいうとpytestのfixtureという機能が関係している。
pytestのfixture
簡単にいうと、「データを作ってテスト関数に渡す仕組み」がpytest fixture
例えば、こういうテストケースがあったとして、
import pytest from app.models import MyAppModel @pytest.mark.django_db def test_app_is_empty(): mymodel = MyAppModel(title="sample model") assert mymodel.is_empty @pytest.mark.django_db def test_app_isnot_emmpty(): mymodel = MyAppModel(title="sample model", content="test") assert not mymodel.is_empty
このように書き換えられる。
import pytest @pytest.fixture def test_model(): from app.models import MyAppModel return MyAppModel @pytest.mark.django_db def test_app_is_empty(test_model): mymodel = test_model(title="sample model") assert mymodel.is_empty @pytest.mark.django_db def test_app_isnot_emmpty(test_model): mymodel = test_model(title="sample model", content="test") assert not mymodel.is_empty
上記の例の場合は、 MyAppModel
のimportのタイミングがモジュールのimportのタイミングではなくなったのが嬉しい。
このようなfixtureをpytest-djangoの中で作成しているからrfやclientが渡ってくる。
pytest-djangoの仕組み
上記の理解を含めて、pytest-djangoのコードを読んでみる。
この辺にある。
まあ、量が多いわけではないので、とりあえずざっと眺めて見た。
pytest_django/fixtures.py
あたりに、rf
とかclient
のfixture定義がある。pytest_django/plugin.py
でfrom .fixtures import rf # noqa
とかの定義がある。pytest
のpluginの探し方をみると、ライブラリをインストールする際の entry_pointを見る。pytest-django
のsetup.py
の中にpytest11
というentry_pointがあり、pytest_django.plugin
につながっている。
はい。
というわけで、pytest_djangoはこんな風にclientやrfを読み込む。
終わり
途中で少し脱線しましたが、とりあえずpytestでdjangoのmodelsとviewをテストする話を書きました。
pytestは奥が深いので持って、いろいろ触っていきたいなー
ではでは。