はじめに
Django Restframework で開発したAPIに対するテストコードを作成する。
そのために必要なノウハウやパッケージなどについてまとめる。
サンプルコード
こちらに作成したコードを参考に以下の話を進めていく。
sampleapp.tests
配下にテスト用のコードを作成している。
使用したライブラリ等
Pythonは3.8.x
- Django 3.0.x
- Django Restframework 3.11.x
- factory-boy 2.11.x
- pytest 4.0.x
- pytest-django 3.4.x
テスト向けのライブラリ説明
factory-boy
テストデータを簡単に作成するためのライブラリ。
https://factoryboy.readthedocs.io/en/latest/
https://github.com/FactoryBoy/factory_boy
pytest, pytest-django
詳細なテスト結果を表示する
https://docs.pytest.org/en/latest/
https://pytest-django.readthedocs.io/en/latest/
テストコードの開発方針(案)
- APIをリクエストし、レスポンスを検証することで一通りの処理をテストする。
- Django-Restframework のViewset でデフォルト作成されるAPIのテストは作らない。
- テストメソッドは、他のテストメソッドに依存しない。
- テストコードは見た目の分かりやすさ重視。コードの共通化などは避け、冗長でも良いので1つ1つ処理を記述する。(作りこみ過ぎるとテスト自体が怪しくなるため)
- テストは、settings.DATABASESに SQLite3を指定して行う。SQLite3の場合は、テストデータはテスト時にメモリに展開されるのみで、物理的な場所は不要。
テスト対象のコードについて
サンプルコードを参照。
settings
この例では、 LimitOffsetPagination
を定義する。
こちらの定義次第で、返却される値が変わるので、テストコードではそれに応じた検証を行う。
通常、テスト時と本番運用時の settings は分ける。サンプルコードでは、 test_settings.py
を用意している。
model, view, serializer
Post
モデルのデータを扱うAPIを作成。
Postが使うステータスの PostStatus
や 子モデルの Comment
をサンプル用に用意。
テストコードの実装
サンプルコードを参照。
Sampleapp の PostモデルのViewset をテストするものとする.
Factoryクラスの準備
ここではテストデータを簡単に作成するためのFactoryクラスを準備する。
Factoryは色々なモジュールで使いまわされることを想定して、 factories.py
というモジュールに外出しして、こちらに全てのFactoryクラスを作成する。
基本的には、1modelクラスに対して、1Factoryクラスを作る。
利用しやすい様、 factory.Sequence
や factory.Faker
など、
適当な初期値が入る様に定義する。
viewのテスト
ここでviewのテストを定義する。
検証内容
- Postの検索で、PostSerializerに設定した全属性が取得できることを確認(※)
- Postの検索で、titleによる前方一致検索が出来ることを確認
- Postの登録で、1件保存できることを確認(※)
上記、テストコードの開発方針(案) で 「Django-Restframework のViewset でデフォルト作成されるAPIのテストは作らない」と記述しているが、サンプルのため敢えてテストコードを作成している。
テスト用のモジュール、クラス、メソッドの分割単位
(テストではない)実コードは、1modelクラスに対して、1viewモジュールを作っている。
- 1viewモジュールに対して、1testモジュールを作成
- 一覧検索系のテストをまとめるテストクラス、1件のCRUDのテストをまとめるテストクラスを作成
- 各クラス内で、対象のテストケース毎にテストメソッドを作成。
Factory によるテストデータの作成
Factoryクラスのインスタンス化で、それぞれのモデルのテストデータが作成される。
# 以下の記載だけでFactoryクラスの定義に従ってデータが出来る
factories.PostFactory()
# titleを明示的に設定したい場合は以下のように定義
factories.PostFactory(title="abcde")
Factoryクラスに、初期値を定義しているので、テストメソッド内で検証する必要がない属性については、明示的に指定しなくて済む。保守性を考え、明示的な指定は必要最低限にして、後はFactoryの初期値に任せる。
as_view() による実行APIの指定
Viewsetのテストでは、 as_view
でViewsetで定義されるCRUDのAPIが全て呼び出せる。
以下のコード例では、一覧検索の例しかないが、 as_view
のパラメータを変えることで、別のAPIを呼ぶことが出来る。
例) {'put', 'update'}
で更新用のAPIが呼べる。下のリンク参照。
https://www.django-rest-framework.org/api-guide/routers/#simplerouter
reverse によるパラメータの指定
また、Restful API ではパラメータの渡し方がいくつかあるが(query, path, body など)、
それぞれ、以下のリンクで設定方法が分かるので参考まで。
APIRequestFactoryによるリクエストの実行
APIRequestFactory を使って、リクエストを実行する。
from django.urls import reverse
from rest_framework.test import APIRequestFactory
# 略
factory = APIRequestFactory()
post_list = PostViewSet.as_view({'get': 'list'})
url = "".join([
reverse('sampleapp:post-list')
])
request = factory.get(url)
response = post_list(request)
テストの実行
python manage.py test [app_name] --settings=drf_sample.test_settings
でもテスト出来るが、ここでは pytest
を使う。
pytest によって、PASSしたテストメソッドが明示され、PASSしなかった場合、詳細な情報が表示される。以下の様な出力がされれば成功。
sampleapp/tests/views/test_post_views.py::PostListTests::test_search_post PASSED [ 33%]
sampleapp/tests/views/test_post_views.py::PostListTests::test_search_post_by_title PASSED [ 66%]
sampleapp/tests/views/test_post_views.py::PostDetailTests::test_create_post PASSED [100%]
pytest.ini
pytest用の設定を行う
- テスト用のsettingsを読み込ませる
- テスト用のモジュールのワイルドカードを定義する
この設定をすれば、 pytest -v
でテスト出来る。
個人的にハマったところ
- tests配下の各ディレクトリに、
__init__.py
を置き忘れて何のテストも実行されない(初歩的・・・) - test用のモジュール名を
pytest.ini
に設定したワイルドカードに合わないtests_xxx.py
としてしまい何のテストも実行されない。
今後の更新予定
こちらのドキュメントは必要に応じて順次、追記していく。
ひとまずテストの取っ掛かりやコード例を示す。
- 異常系のテストサンプルも作成。
- mock を使ったテストなど、まだ記述したい内容あり。順次追加する。