Django REST frameworkのユニットテスト


単体テストをExcelとかに絶対に管理したくないマンなのでテストの機能が備わっているものなら是非とも使いたい。
Pythonもとい、Djangoにもしっかりテストの機能はついている。

もちろんDjangoのテストの記事なんていっぱいあるのだが、どれを真似してもなんかAPIが上手く動かない...

冷静に考えたらREST framework使っているのに普通のDjangoのテストを参考にしても上手くいかないよね。
というわけで個人的な備忘録として記載していく。別に公式ページをつど見るのもいいんだけど英語ってあんまり読みたくないじゃん
一応ソースもgithubで公開中

モデルの準備

とりあえずモデルを準備

models.py
class SampleTable(models.Model):
    name = models.CharField(verbose_name="氏名", max_length=10)
    phone = models.IntegerField(verbose_name="電話番号")

サンプルなんで中身は適当

テスト対象のAPIの作成


テストしたいviewsの作成。今回はGET通信で。
views.py
class SampleGet(generics.ListAPIView):
    queryset = SampleTable.objects.all()
    serializer_class = SampleSerializer
urls.py
urlpatterns = [
    path(r'sampleget', SampleGet.as_view(), name='detail'),
]

テストの作成

フォルダ構成はこんな感じ

project
├── project
├── app
│   ├── migrations
│   ├── tests
│   │   ├── __init__.py
│   │   └── test_views.py
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── models.py
│   ├── serializer.py
│   ├── urls.py
│   └── views.py
├── manage.py

デフォルトでtests.pyがいるけど、それは使わないでtestsフォルダを作成。その中にinit.py(忘れがち)とテスト用のソースを作成。
ソースの名前はtest_対象のソース.pyってなるようにする。今回はtest_views.py。

そして中身はこんな感じ

test_views.py
class APIViewTests(TestCase):
    @classmethod
    def setUpTestData(cls):
        SampleTable.objects.create(name="testName", phone="000")
        SampleTable.objects.create(name="sampleName", phone=111)

    def test_sample_get_api(self):
        factory = APIRequestFactory()
        view = SampleGet.as_view()
        url = "http://127.0.0.1:8000/api/sampleget"
        request = factory.get(url)
        response = view(request)

        self.assertEquals(len(response.data), 2)
        self.assertEquals(response.data[0]["name"], "testName")
        self.assertEquals(response.data[1]["phone"], 111)

ではわかる限り詳細を。まず最初のsetUpTestDataについて

test_views.py
def setUpTestData(cls):
        SampleTable.objects.create(name="testName", phone="000")
        SampleTable.objects.create(name="sampleName", phone=111)

初期データの作成。Djangoでは、settings.pyに書き込めば本番DBの構成をコピーしたテスト用の DBを作ってくれる

settings.py
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'mysql',
        'USER': 'mysql',
        'PASSWORD': 'mysql',
        'HOST': 'db',
        'PORT': '3306',
        'TEST': {
            'NAME': 'test_mysql',
        },
    }
}

テスト用のDBはデータが空のため初期データを準備する必要がある。もちろんテスト次第では省いて良い。
今回はGET通信のテストのため2件ほど作成しておく。

次に実際にテスト内容を記載する部分。

test_views.py
def test_sample_get_api(self):
        factory = APIRequestFactory()
        view = SampleGet.as_view()
        url = "http://127.0.0.1:8000/api/sampleget"
        request = factory.get(url)
        response = view(request)

最初はリクエストを作るのにrequest.session()とか使っていたけど、それだとエラーが起きたり、本番用DBに繋がったりなんやかんや上手くいかなかった。正しくはいかにもって感じの名前のAPIRequestFactory()を使えばいいみたい。あとは対象のURLを使ってリクエストを作成。

もちろんrequest = factory.get(url)のgetの部分はAPIに合わせてpostやputなんかに変えることができる。最後にviewを使ってレスポンスを取得したら終わり。あとは好きにテスト処理を書けばよし。

mysqlだと

あとはコンソールで以下のコマンドを実行すればテストが行われる。

python manage.py test

だけどこんなエラーが来ることも

Got an error creating the test database: (1044, "Access denied for user 'mysql'@'%' to database 'test_mysql'")

mysqlの権限が足りなくてテスト用のDBが作れなかったよという感じ(のはず)のエラーが出てくる。

mysql> GRANT ALL PRIVILEGES ON test_mysql.* TO 'mysql'@'%';

大体上のコマンドを実行しておけばどうにかなる。改めて実行するとこんな感じでテスト成功のメッセージが表示されてテストが完了。

Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.
----------------------------------------------------------------------
Ran 1 test in 0.011s

OK
Destroying test database for alias 'default'...

あとはAPIによっては承認が必要なもがあったりして、その時は無理やり承認させたりとかできるのでその方法とかは実装次第また追記します、