エンジニアライフスタイルブログを運営しているミウラ(@miumiu06171)です。
普段はフリーランスでシステムエンジニアをしております。
今回は、こちらの記事で作成したDjango Rest Framework(DRF)のAPIサーバをもとにテストの書き方・テスト実装方法・テスト実行方法を解説していきます。
APIサーバをテストするためにVisual Studio Code (VS Code) を使用しているため、同様に確認したい方は、こちらの記事でVS CodeでPythonの開発環境を構築してみてください。
APIサーバの基本動作
APIサーバの基本動作を説明する前にAPIサーバのおさらいをしましょう。
APIサーバとは、クライアント・サーバ型のアプリケーションおいてAPI(Application Programming Interface)をサービスとして提供するコンピュータのことです。
このAPIサーバの基本動作は、CRUDという処理に集約されます。
CRUDとは、Create(作成)、Read(読み込み)、Update(更新)、Delete(削除)の頭文字をとったもので、Webアプリケーションの基本動作とも言えます。
このCRUD処理について、図を使ってもう少し詳しく見ていきましょう。
CRUD処理(Create):POSTメソッド
CRUD処理のうちCreateを行うとき、下図のようにAPIサーバに対してPOSTメソッドという処理を実行します。
上図は、ユーザからAPIサーバに対して「商品一覧にみかんを追加して」というリクエストをPOSTメソッドで行っている例です。
CRUD処理(Read):GETメソッド
CRUD処理のうちReadを行うとき、下図のようにAPIサーバに対してGETメソッドという処理を実行します。
上図は、ユーザからAPIサーバに対して「商品一覧がほしい」というリクエストをGETメソッドで行っている例です。
CRUD処理(Update):PATCH / PUT メソッド
CRUD処理のうちUpdateを行うとき、下図のようにAPIサーバに対してPatchメソッドまたはPUTメソッドという処理を実行します。
上図は、ユーザからAPIサーバに対して「商品一覧にあるみかんをメロンに変更して」というリクエストをPatchまたはPUTメソッドで行っている例です。
CRUD処理(Delete):DELETEメソッド
CRUD処理のうちDeleteを行うとき、下図のようにAPIサーバに対してDELETEメソッドという処理を実行します。
上図は、ユーザからAPIサーバに対して「商品一覧からメロンを削除して」というリクエストをDELETEメソッドで行っている例です。
CRUD処理の説明は以上になります。
簡単な例でCRUDについて説明してきましたが、ほぼすべてのWebアプリケーションに同じような機能が備わっています。
Twitterであれば、ツイート投稿したり、過去ツイートを閲覧したり、ツイートを更新したり、ツイートを削除したりといった具合です。
Django RestFramework(DRF)のAPIサーバテスト実装方法
CRUD処理のイメージがわいたところで、こちらの記事のDjango Rest Frameworkで作成したAPIサーバのテスト実装方法について見ていきましょう。
Django Rest Frameworkのテストで主に使うモジュール
Django Rest Frameworkのテストで主に使うモジュールは、以下のとおりになります。
モジュール | 説明 |
---|---|
get_user_model | Djangoのユーザ管理で扱っているユーザモデルを取得するモジュールです。 |
reverse | DjangoのモデルのレコードからURLに逆変換するためのモジュールです。 |
TestCase | Djangoのテストモジュールです。 |
status | HTTPステータスを扱うモジュールです。 |
APIClient | APIサーバをテストするために用意されたAPIクライアントモジュールです。 |
上記モジュールをテストファイルで扱うためには、下記のようにインポートする必要があります。
1 2 3 4 5 |
from django.contrib.auth import get_user_model from django.urls import reverse from django.test import TestCase from rest_framework import status from rest_framework.test import APIClient |
Django Rest Frameworkのテストの書き方
次は、Django Rest Frameworkのテストの書き方を解説していきます。
テストファイルの作成
まずは、Djangoアプリ内にテストファイルを作成する必要があります。
上図は、Djangoアプリである「app」というフォルダ内に赤枠で示すテストファイルを作成しています。
テストファイルの命名ルールとして、「test」から始まるファイル名である必要があります。
クラスでテストを括る
次にテストファイル内にどのようにテストを記述するか見ていきます。
テストは、Pythonのクラスとメソッドで記述します。
上記の赤枠で示すPythonのクラスは、APIサーバ内の商品テーブルに対するテストをまとめています。
つまり、Pythonのクラスでテスト単位を区分けすることができます。
メソッドでテストを実装する
テストコード自体は、Pythonのメソッドで実装します。
Pythonのメソッドの中でも下図の赤枠に示すsetUpメソッドは、特殊なメソッドになります。
setUpメソッドは、各テストの実行直前に必ず実行される特別なメソッドです。
そのため、クラス内に定義した全テストケースの実行前に処理したい初期化処理などを入れるとよいでしょう。
そして、下図の赤枠に示すように通常のメソッドを定義し、メソッド内にテストを実装します。
Django Rest Frameworkのテストの書き方は、以上になります。
次からAPIサーバのCRUD処理について、テスト実装方法をみていきます。
CRUD処理のテスト実装(共通部)
CRUD処理のテストで共通する処理をまず定義します。
商品テーブルのCRUD処理において、テストの共通部分である下記コードをもとに解説していきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
from .models import ItemCategoryModel, ItemModel from .serializers import ItemSerializer # エンドポイントをあらかじめ定義しておく ITEMCATEGORY_URL = '/app/itemcategory/' ITEM_URL = '/app/item/' # ItemGategoryを作成する関数 def create_itemcategory(item_category_name): return ItemCategoryModel.objects.create(item_category_name=item_category_name) # itemを作成する関数 def create_item(**params): defaults = { 'item_name': 'りんご', } defaults.update(params) return ItemModel.objects.create(**defaults) # 特定のItemCategoryへのURLを作成する関数 def detail_itemcategory_url(item_category_id): return reverse('app:itemcategorymodel-detail', args=[item_category_id]) # 特定のItemへのURLを作成する関数 def detail_item_url(vehicle_id): return reverse('app:itemmodel-detail', args=[vehicle_id]) |
CRUD処理のテストをする上で、用意したデータとデータベースに書かれているデータが同じかチェックする必要がでてきます。
そのため、テストファイル内でデータベースの内容を参照するため、対象となるDjangoのモデルとSerializerをインポートします。
5~6行目は、APIサーバのエンドポイントを定義しています。
8~19行目は、商品テーブルをテストするために商品カテゴリと商品を作成するための関数を用意しています。
21~27行目は、特定の商品カテゴリIDまたは商品IDからURLを生成する関数を用意しています。
CRUD処理(Create)のテスト実装:POSTメソッド
テストの共通部分のコードを理解したところで、CRUD処理のCreateについてテストコードを見ていきましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
# ItemのCRUD処理のテスト class ItemApiTests(TestCase): def setUp(self): self.client = APIClient() # POSTメソッド(1) # 新規でitemを作成 def test_2_3_should_create_new_item_successfully(self): itemcategory = create_itemcategory(item_category_name='フルーツ') payload = { 'item_name': 'みかん', 'item_category_id': itemcategory.id, } res = self.client.post(ITEM_URL, payload) # DBのデータ取得 item = ItemModel.objects.get(id=res.data['id']) # status code確認 self.assertEqual(res.status_code, status.HTTP_201_CREATED) # DB内容とpayloadで渡したparamsが一致するか確認 self.assertEqual(payload['item_name'], item.item_name) self.assertEqual(payload['item_category_id'], item.item_category_id.id) |
4~5行目は、setUpメソッドでAPIクライアントを用意しています。
APIサーバをテストするためにAPIクライアントはどのテストでも必須であるため、setUpメソッドでAPIクライアントを定義しています。
10行目は、商品を作成するにあたって商品がどの商品カテゴリか指定する必要があるため、一つ商品カテゴリを作成しています。
11~15行目は、商品作成するためのデータを用意し、POSTメソッドでリクエストをなげています。
19行目は、POSTメソッドによって正しく商品が作成されたかHTTPのステータスをチェックしています。
17行目は、APIサーバ内のデータベースからPOSTメソッドで生成された商品IDの商品を取得しています。
21~22行目は、商品作成するために用意したデータと、データベースから取得したデータが一致するか確認しています。
これにより、POSTメソッドで正しく商品情報が書き込まれたかテストできます。
CRUD処理(Read)のテスト実装:GETメソッド
次は、CRUD処理のReadについてテストコードを見ていきましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# GETメソッド(1) # item一覧を取得できるか確認 def test_2_1_should_get_item(self): itemcategory = create_itemcategory(item_category_name='フルーツ') create_item(item_name='りんご', item_category_id=itemcategory) create_item(item_name='みかん', item_category_id=itemcategory) res = self.client.get(ITEM_URL) items = ItemModel.objects.all().order_by('id') serializer = ItemSerializer(items, many=True) self.assertEqual(res.status_code, status.HTTP_200_OK) self.assertEqual(res.data, serializer.data) |
4~6行目は、GETメソッドによって商品一覧を取得できるかテストするため、事前に「りんご」と「みかん」の2つのテストデータをデータベースに書き込んでいます。
9~10行目は、先程データベースに書き込んだ商品の一覧を取得し、ItemSerializerを通してデータベースから必要なFieldをPythonのdict型で保持しています。
8行目は、GETメソッドで商品一覧を取得しています。
13行目は、GETメソッドで取得したデータと、9~10行目でデータベースから直接リードしてItemSerializerを通したデータを比較してテストしています。
GETメソッドをテストする場合、GETメソッドで取得したデータと、データベースのモデルから直接読みだしたデータを比較する必要があります
しかし、データベースから直接読みだしたデータを比較対象にするのは不十分で、Serializerを通したデータを比較対象にする必要があります。
理由は、下図のGETメソッドで得られるデータがSerializerを通して出力に必要なフィールドを選択されたデータであるため、比較対象も同じようにフィールドを選択された後のデータである必要があるからです。
CRUD処理(Update)のテスト実装:Patchメソッド
次は、CRUD処理のUpdateについて、Patchメソッドのテストコードを見ていきましょう。
1 2 3 4 5 6 7 8 9 10 |
# Patchメソッド def test_2_5_should_partial_update_item(self): itemcategory = create_itemcategory(item_category_name='フルーツ') item = create_item(item_name='りんご', item_category_id=itemcategory) # PATCHの場合は変更対象の属性をpayloadで指定するだけでよい payload = {'item_name': 'みかん'} url = detail_item_url(item.id) self.client.patch(url, payload) item.refresh_from_db() self.assertEqual(item.item_name, payload['item_name']) |
6~8行目は、商品「りんご」を「みかん」に変更するためのデータを用意し、Patchメソッドを実行しています。
9行目は、1~2行目で事前に書き込んでいた商品「りんご」に紐付いた変数を、最新のデータベースの値にリフレッシュしています。
10行目は、Patchメソッドで商品「みかん」に変更されているか確認しています。
CRUD処理(Update)のテスト実装:PUTメソッド
次は、CRUD処理のUpdateについて、PUTメソッドのテストコードを見ていきましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# PUTメソッド def test_2_6_should_update_item(self): itemcategory = create_itemcategory(item_category_name='フルーツ') item = create_item(item_name='りんご', item_category_id=itemcategory) # PUTの場合は全属性をpayloadで指定する必要あり payload = { 'item_name': 'みかん', 'item_category_id': itemcategory.id, } url = detail_item_url(item.id) self.assertEqual(item.item_name, 'りんご') self.client.put(url, payload) item.refresh_from_db() self.assertEqual(item.item_name, payload['item_name']) |
6~13行目は、商品「りんご」を「みかん」に変更するためのデータを用意し、PUTメソッドを実行しています。
14行目は、Patchメソッドのテストと同様に変数のデータを、最新のデータベースのデータに更新しています。
15行目でPUTメソッドで商品「みかん」に変更されているか確認しています。
CRUD処理(Delete)のテスト実装:DELETEメソッド
最後は、CRUD処理のDeleteについて、DELETEメソッドのテストコードを見ていきましょう。
1 2 3 4 5 6 7 8 9 |
# Deleteメソッド def test_2_7_should_delete_item(self): itemcategory = create_itemcategory(item_category_name='フルーツ') item = create_item(item_name='りんご', item_category_id=itemcategory) self.assertEqual(1, ItemModel.objects.count()) url = detail_item_url(item.id) self.client.delete(url) self.assertEqual(0, ItemModel.objects.count()) |
3~5行目は、商品「りんご」を作成した後、データベース内のレコード数が1になっていることを確認しています。
7~9行目は、商品「りんご」を削除した後、データベース内のレコード数が0になっていることを確認しています。
次は、商品カテゴリ「フルーツ」を削除すると、CASCADE設定によって商品カテゴリ「フルーツ」に紐付いた商品が連動して削除されることを確認します。
1 2 3 4 5 6 7 8 9 10 |
# Deleteメソッド:CASCADE def test_2_8_should_cascade_delete_item_by_itemcategory_delete(self): itemcategory = create_itemcategory(item_category_name='フルーツ') item = create_item(item_name='りんご', item_category_id=itemcategory) self.assertEqual(1, ItemModel.objects.count()) # itemcategoryのURL取得し、itemcategoryを削除 url = detail_itemcategory_url(itemcategory.id) self.client.delete(url) self.assertEqual(0, ItemModel.objects.count()) |
3~5行目は、商品「りんご」を作成した後、データベース内のレコード数が1になっていることを確認しています。
8~10行目は、商品カテゴリ「フルーツ」のURLを取得して削除した後、データベース内の商品レコード数が0になっていることを確認しています。
これでCRUD処理のテスト実装は、以上になります。
Django RestFramework(DRF)のAPIサーバテスト実行
テストの実装が終わったところで、テストの実行方法をみていきましょう。
Django Rest Frameworkで作成したAPIサーバのテストを実行するには、以下のtestコマンドを実行します。
1 |
python manage.py test -v 2 |
コマンドオプション「-v 2」を付加することで、下図の赤枠のようにテスト単位で結果を出力してくれます。
まとめ
いかがでしたでしょうか。
Django Rest Framework(DRF)で作成したAPIサーバのテストの書き方・テスト実装方法・テスト実行方法を解説してきました。
初心者の方にもわかりやすいようにGitHubに本記事のテストコードも公開しておりますので、参考にして頂ければ幸いです。
【関連記事】
【TIPS】Django RestFramework(DRF)のAPIサーバ開発手順と動作確認方法
【TIPS】GitHubにあるDjangoおよびDRFの開発環境を再現する方法[Windows版]
【TIPS】CORSインストールでDjango(DRF)とReactを連携する方法まとめ
【GitHub】
本記事のソースコードは、こちらを参照ください。