Django Restframework のテストコードを作成する

# はじめに

Django Restframework で開発したAPIに対するテストコードを作成する。
そのために必要なノウハウやパッケージなどについてまとめる。

# サンプルコード

こちらに作成したコードを参考に以下の話を進めていく。
`sampleapp.tests` 配下にテスト用のコードを作成している。

https://github.com/shinno21/drf_sample

# 使用したライブラリ等

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のテストは作らない。
* テストメソッドは、他のテストメソッドに依存しない。
* テストコードは見た目の分かりやすさ重視。コードの共通化などは避け、冗長でも良いので１つ１つ処理を記述する。（作りこみ過ぎるとテスト自体が怪しくなるため）
* テストは、settings.DATABASESに SQLite3を指定して行う。SQLite3の場合は、テストデータはテスト時にメモリに展開されるのみで、物理的な場所は不要。
         
# テスト対象のコードについて

サンプルコードを参照。

https://github.com/shinno21/drf_sample/tree/master/sampleapp

## settings

この例では、 `LimitOffsetPagination` を定義する。
こちらの定義次第で、返却される値が変わるので、テストコードではそれに応じた検証を行う。

通常、テスト時と本番運用時の settings は分ける。サンプルコードでは、 `test_settings.py` を用意している。

## model, view, serializer

`Post` モデルのデータを扱うAPIを作成。
Postが使うステータスの `PostStatus` や 子モデルの `Comment` をサンプル用に用意。

# テストコードの実装

サンプルコードを参照。

https://github.com/shinno21/drf_sample/tree/master/sampleapp/tests

Sampleapp の PostモデルのViewset をテストするものとする.

## Factoryクラスの準備

ここではテストデータを簡単に作成するためのFactoryクラスを準備する。

Factoryは色々なモジュールで使いまわされることを想定して、 `factories.py` というモジュールに外出しして、こちらに全てのFactoryクラスを作成する。
基本的には、1modelクラスに対して、1Factoryクラスを作る。

利用しやすい様、 `factory.Sequence` や　 `factory.Faker` など、
適当な初期値が入る様に定義する。

## viewのテスト

ここでviewのテストを定義する。

### 検証内容

* Postの検索で、PostSerializerに設定した全属性が取得できることを確認（※）
* Postの検索で、titleによる前方一致検索が出来ることを確認
* Postの登録で、１件保存できることを確認（※）

[上記、テストコードの開発方針（案）](https://qiita.com/shinno21/items/a42eea9b6ce959e106c0#%E3%83%86%E3%82%B9%E3%83%88%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AE%E9%96%8B%E7%99%BA%E6%96%B9%E9%87%9D%E6%A1%88) で 「Django-Restframework のViewset でデフォルト作成されるAPIのテストは作らない」と記述しているが、サンプルのため敢えてテストコードを作成している。

### テスト用のモジュール、クラス、メソッドの分割単位

（テストではない）実コードは、1modelクラスに対して、1viewモジュールを作っている。

* 1viewモジュールに対して、1testモジュールを作成
* 一覧検索系のテストをまとめるテストクラス、１件のCRUDのテストをまとめるテストクラスを作成
* 各クラス内で、対象のテストケース毎にテストメソッドを作成。


### Factory によるテストデータの作成

Factoryクラスのインスタンス化で、それぞれのモデルのテストデータが作成される。

```python
# 以下の記載だけで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 など）、
それぞれ、以下のリンクで設定方法が分かるので参考まで。

https://qiita.com/shinno21/items/daa46a6f5f1df29a24af

### APIRequestFactoryによるリクエストの実行

APIRequestFactory を使って、リクエストを実行する。

```python
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 -v --ds=sampleapp.test_settings
```

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 

https://github.com/shinno21/drf_sample/blob/master/pytest.ini

pytest用の設定を行う
* テスト用のsettingsを読み込ませる
* テスト用のモジュールのワイルドカードを定義する

この設定をすれば、 `pytest -v` でテスト出来る。


# 個人的にハマったところ

* tests配下の各ディレクトリに、 `__init__.py` を置き忘れて何のテストも実行されない（初歩的・・・）
* test用のモジュール名を `pytest.ini` に設定したワイルドカードに合わない `tests_xxx.py` としてしまい何のテストも実行されない。

# 今後の更新予定

こちらのドキュメントは必要に応じて順次、追記していく。
ひとまずテストの取っ掛かりやコード例を示す。

* 異常系のテストサンプルも作成。
* mock を使ったテストなど、まだ記述したい内容あり。順次追加する。