APIを自動テストする

APIの自動テスト色々なテストコードを作成してみよう

↓↓↓はじめて講座を受ける方はこちら↓↓↓
開発環境を作ろう

色々なテストコードを作成してみよう の講座の4レッスン目。

今回はAPIの自動テスト方法を学んで行きます。

前回のレッスンはこちら↓

ウサギ
ウサギ

前回はバッチの自動テスト方法を学んだな

わいへい
わいへい

あと。。。なんかあるっけ?

ウサギ
ウサギ

重要なテストがまだあるぞ。

APIのテストだ。

ウサギ
ウサギ

今回はお前の日記アプリにAPIを追加して、テストコードを書いてみるぞ

テスト対象の準備

例のごとくお前のクソ日記アプリの雛形をクローンしておくぞ。

リポジトリのクローン

GitHub - yheihei/base_diary: ウサでも分かる中級Django講座の教材
ウサでも分かる中級Django講座の教材. Contribute to yheihei/base_diary development by creating an account on GitHub.
$ git clone https://github.com/yheihei/base_diary.git
$ cd base_diary

テストコードだけ見る場合

ウサギ
ウサギ

正解を書いたブランチは下記だ。テストコードだけ見たいやつは見てくれ

GitHub - yheihei/base_diary at feature/#5
ウサでも分かる中級Django講座の教材. Contribute to yheihei/base_diary development by creating an account on GitHub.
# テストコードがすでに書いてあるブランチを見たい場合はこちら
$ git fetch && git checkout feature/#5

テスト対象の解説

ウサギ
ウサギ

忘れてると思うから、もう一度日記アプリの仕様を確認する。

トップページにアクセスすると、こんな画面が出てくる

クソ日記アプリ

仕様は下記。

  • トップページにアクセスすると、日記一覧が表示される
  • 各日記には下記が表示される
    • タイトル
    • 投稿者
    • 本文
    • 投稿日
    • カテゴリー

今回実装する機能

ウサギ
ウサギ

今回は投稿のAPIを作ってみる。エンドポイントと機能は下記の通りだ

methodエンドポイント名前どんな動作か
GET/api/posts/投稿一覧取得・全記事のリストを取得できる
・タイトルと本文をキーワード検索できる
・更新時刻、作成時刻で並び替えができる
・カテゴリで検索できる
POST/api/posts/新規投稿・タイトルと本文を指定して記事を投稿できる
GET/api/posts/:id/投稿取得・特定の記事のタイトル、本文、カテゴリ、投稿者情報を取得できる
PUT/api/posts/:id/投稿更新・特定の記事を指定したタイトル、本文で更新できる
PATCH/api/posts/:id/投稿一部更新・特定の記事を、タイトルだけ、本文だけ、など一部のフィールドだけ更新できる
DELETE/api/posts/:id/投稿削除・特定の記事を削除できる
投稿に関するAPI仕様

※ 全APIエンドポイントは投稿者に紐ついたトークン認証もしくはセッションによるログイン認証を行わなければならない

※ 認証に失敗したらステータスコード401が返却され、動作に失敗する

ウサギ
ウサギ

例えば、トークン認証をしながら、投稿一覧取得のAPIを叩くと、こんな感じに返ってくる

$ curl -X GET http://127.0.0.1:8000/api/posts/ -H 'Authorization: Token 18c26d59a34efb80ee100d55642aa644b1dc9a52'
[
	{
		"id": 1,
		"user": {
			"id": 1,
			"username": "yhei",
			"email": "yheihei0126@gmail.com"
		},
		"title": "一つ目の日記",
		"body": "最初の日記だよ",
		"created_at": "2021-07-13T05:10:40.097732Z",
		"updated_at": "2021-07-24T05:31:37.578064Z",
		"categories": [
			{
				"name": "日記",
				"slug": "diary"
			}
		]
	},
	{
		"id": 2,
		"user": {
			"id": 1,
			"username": "yhei",
			"email": "yheihei0126@gmail.com"
		},
		"title": "二つ目の日記",
		"body": "この日記は2つ目の日記だよ",
		"created_at": "2021-07-04T05:11:13.196329Z",
		"updated_at": "2021-07-22T13:59:50.294969Z",
		"categories": [
			{
				"name": "雑記",
				"slug": "general"
			}
		]
	}
]

機能追加する

わいへい
わいへい

めちゃくちゃAPIあんじゃん!?

これ、今から作るの??? めっちゃ長くなるぞ?

ウサギ
ウサギ

うむ。スクラッチで書くのはめんどすぎるので

ウサギ
ウサギ

django rest frameworkを使って、手抜きするぞ

この講座はテストのやり方の解説なので、django rest frameworkについては深くは説明しない。

下記の講座でdjango rest frameworkを使ったAPIの作り方を書いているから、気になるやつはそちらを見てくれ。

djnago rest frameworkを使ったAPIの作成方法(準備中)

必要なパッケージをインストールする

ウサギ
ウサギ

こっからは基本的にコピペで良い

ウサギ
ウサギ

なぜならAPIのレッスンではなく、テストコードのレッスンだから

気にせず走り抜けろ!

まずはパッケージインストール。

$ pip install djangorestframework
$ pip install django-filter

djangorestframeworkはAPIの作成を楽にしてくれるパッケージだ。

少ない記述でリッチなAPIが実現できる。

django-filterはAPIの検索条件を記述するときに使えるパッケージだ。

2つを組み合わせることで、APIの作成工数が死ぬほど減る。

settings.pyに設定を書く

次はbasediary/settings.pyをいじっていく。

INSTALLED_APPSにrest_frameworkrest_framework.authtokendjango_filtersのアプリを追加し、django rest frameworkの設定を追加する。

INSTALLED_APPS = [
    ...
    'rest_framework',
    'rest_framework.authtoken',  #rest_frameworkにトークン認証の機能をつける
    'django_filters',
]

...

REST_FRAMEWORK = {
    'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'],
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    ),
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.TokenAuthentication',
        'rest_framework.authentication.SessionAuthentication',
    )
}

シリアライザーにAPIで返したい値の定義を書く

次にdiary/serializers.pyを作り、APIで返したい値の定義を書く。

from django.db.models import fields
from diary.models import Post, Category, User
from rest_framework import serializers


class UserSerializer(serializers.ModelSerializer):
  class Meta:
    model = User
    fields = ('id', 'username', 'email')


class CategorySerializer(serializers.ModelSerializer):
  class Meta:
    model = Category
    fields = ('name', 'slug')


class PostSerializer(serializers.ModelSerializer):
  user = UserSerializer(read_only=True)
  categories = CategorySerializer(many=True, read_only=True)

  class Meta:
    model = Post
    fields = ('id', 'user', 'title', 'body', 'created_at', 'updated_at', 'categories')

ModelSerializerを継承してmodelを指定することで、各モデルのプロパティを勝手に返すようになる。

さっき投稿一覧取得のAPIを叩いた時の、下記レスポンスの定義を↑で書いたってわけ。

[
	{
		"id": 1,
		"user": {
			"id": 1,
			"username": "yhei",
			"email": "yheihei0126@gmail.com"
		},
		"title": "一つ目の日記",
		"body": "最初の日記だよ",
		"created_at": "2021-07-13T05:10:40.097732Z",
		"updated_at": "2021-07-24T05:31:37.578064Z",
		"categories": [
			{
				"name": "日記",
				"slug": "diary"
			}
		]
	},
...
...

views.pyにAPIがコールされた時の挙動を書く

続いて、diary/views.pyを下記のように書き換える。

from django.shortcuts import render

# Create your views here.
from .models import Post, User
from rest_framework import viewsets, filters
from .serializers import PostSerializer
from rest_framework.authentication import TokenAuthentication, SessionAuthentication, BasicAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.decorators import permission_classes, authentication_classes

# Create your views here.
def index(request):
  return render(request, 'index.html', {
    'posts': Post.objects.all(),
  })


class CategoryFilter(filters.BaseFilterBackend):
  def filter_queryset(self, request, queryset, view):
    if request.query_params.get('category'):
      return queryset.filter(categories__slug=request.query_params.get('category'))
    return queryset


class PostViewSet(viewsets.ModelViewSet):
  queryset = Post.objects.all()
  serializer_class = PostSerializer
  filter_backends = [
    filters.SearchFilter,
    filters.OrderingFilter,
    CategoryFilter,
  ]
  search_fields = ('title', 'body')
  ordering_fields = ('created_at', 'updated_at')

  def perform_create(self, serializer):
    serializer.save(user=self.request.user)

エンドポイントが呼ばれると、PostViewSetが呼び出される。

その中で、

serializer_class = PostSerializer

で、先ほど書いたシリアライザーをもとにdjango rest frameworkがレスポンスを勝手に作成する。

さらに言えば

class PostViewSet(viewsets.ModelViewSet):
  queryset = Post.objects.all()
  serializer_class = PostSerializer

ここでModelViewSetを継承しつつ、querysetでPost(投稿モデル)の取得を定義。

さらに、serializer_class = PostSerializerでPost(投稿モデル)をmodelにもつシリアライザーを指定する。

すると、django rest frameworkがPost(投稿モデル)に関するCRUDのAPIの処理を勝手に作ってくれる。

urls.pyにエンドポイントの設定を書く

続いて、diary/urls.pyを下記のように書き換える。

from django.urls import path
from django.conf.urls import url, include
from . import views
from rest_framework import routers
from rest_framework.authtoken import views as auth_views

app_name = "diary"

router = routers.DefaultRouter()
router.register(r'posts', views.PostViewSet)

urlpatterns = [
  path("", views.index, name="index"),
  url(r'^api/', include(router.urls)),
  url(r'^api-token-auth/', auth_views.obtain_auth_token),
]

これだけで、下記のエンドポイントが勝手に作られる。

methodエンドポイント名前
GET/api/posts/投稿一覧取得
POST/api/posts/新規投稿
GET/api/posts/:id/投稿取得
PUT/api/posts/:id/投稿更新
PATCH/api/posts/:id/投稿一部更新
DELETE/api/posts/:id/投稿削除
わいへい
わいへい

django rest frameworkすごすぎだろ。。。

APIの疎通

最後にマイグレーションをして。サーバーを起動する。

$ python manage.py migrate
$ python manage.py runserver

その後、ログアウトした上http://127.0.0.1:8000/api/posts/ にアクセスしてみてくれ。

わいへい
わいへい

なんか認証エラーみたいなのが出たけど???

そうだ。もう一度仕様を振り返ってみよう。

※ 全APIエンドポイントは投稿者に紐ついたトークン認証もしくはセッションによるログイン認証を行わなければならない

※ 認証に失敗したらステータスコード401が返却され、動作に失敗する

認証がされていなければ、APIにアクセスすることはできないんだ。

settings.pyに下記を書いたのを覚えているか?

REST_FRAMEWORK = {
    'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'],
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',  # 全API認証必須
    ),
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.TokenAuthentication',  # トークン認証
        'rest_framework.authentication.SessionAuthentication',  # ログインでの認証
    )
}

DEFAULT_PERMISSION_CLASSES'rest_framework.permissions.IsAuthenticated'を指定することで、全APIのエンドポイントが認証必須になる。

さらに、DEFAULT_AUTHENTICATION_CLASSESで認証方法は何よ? ってのを決めている。

'rest_framework.authentication.TokenAuthentication'を指定することで、トークン認証で認証が行えるようになる。

'rest_framework.authentication.SessionAuthentication'を指定することで、ログインしているユーザーならAPIにアクセス可能になる。つまりセッションによるログイン認証だ。

わいへい
わいへい

だからログアウトした状態だと、APIにアクセスできないのか

そうだ。それじゃあ、ログインした上でもう一度http://127.0.0.1:8000/api/posts/にアクセスしてみよう。

わいへい
わいへい

おおおおお。日記一覧のデータが返ってきた!!!!

トークン認証でのAPI疎通

ブラウザからAPIにアクセスできることは確認できたな?

ただ、このAPIを使う奴はブラウザだけじゃない。

例えばモバイルのアプリが呼び出すかもしれない。

わいへい
わいへい

そう言えば、ブラウザ以外の奴がログインするってどうやってやるの?

そんなときに使うのがトークン認証だ。

流れとしては、

  • トークン取得のAPIを、ログイン情報と一緒にコール、トークンを取得する
  • 取得したトークンをヘッダーにつけて、APIを呼び出す

だ。

実際にcurlコマンドでやってみよう。

まずはトークン取得のAPIをコールする。ログインユーザーとパスワードを入れてコールしてやる。

$ curl -H "Accept: application/json" -H "Content-type: application/json" -X POST -d '{"username":"yhei","password":"password"}' http://127.0.0.1:8000/api-token-auth/

{"token":"7e033a902ac180e0231fced74ba58fcd658c7bbf"}

こうするとそのユーザー用のtokenが発行されるので、

次はtokenをヘッダーにつけて投稿一覧取得のAPIを呼んでみる。

$ curl -X GET http://127.0.0.1:8000/api/posts/ -H 'Authorization: Token 7e033a902ac180e0231fced74ba58fcd658c7bbf'
[{"id":1,"user":{"id":1,"username":"yhei","email":"yheihei0126@gmail.com"},"title":"一つ目の日記","body":"最初の日記だよ","created_at":"2021-07-13T05:10:40.097732Z","updated_at":"2021-07-24T05:31:37.578064Z","categories":[{"name":"日記","slug":"diary"}]},{"id":2,"user":{"id":1,"username":"yhei","email":"yheihei0126@gmail.com"},"title":"二つ目の日記","body":"この日記は2つ目の日記だよ","created_at":"2021-07-04T05:11:13.196329Z","updated_at":"2021-07-22T13:59:50.294969Z","categories":[{"name":"雑記","slug":"general"}]},{"id":3,"user":{"id":1,"username":"yhei","email":"yheihei0126@gmail.com"},"title":"title","body":"パッチで更新してみた","created_at":"2021-07-22T15:51:34.655604Z","updated_at":"2021-07-22T17:33:10.124466Z","categories":[]}]
わいへい
わいへい

おお、日記のjsonが取れた!!!

ウサギ
ウサギ

API疎通の確認は以上だ

ウサギ
ウサギ

いよいよテストコードを書いていくぞ

テストコードを書く

日記のデータをfixtureで入れる

まずは何においてもテストデータの投入だ。

diary/fixtures/test/test_api_posts.json を作って、下記のデータを入れる。

[
  {
      "model": "diary.user",
      "pk": 1,
      "fields": {
          "password": "pbkdf2_sha256$180000$vYCnAMtlYpZj$GlAfVlJZP6X4qTF2mcRKYHnBKOr5cFmWVdE1HVLZJOc=",
          "last_login": "2021-07-13T05:09:45.196Z",
          "is_superuser": true,
          "username": "yhei",
          "first_name": "",
          "last_name": "",
          "email": "yheihei0126@gmail.com",
          "is_staff": true,
          "is_active": true,
          "date_joined": "2021-07-13T05:09:21.149Z",
          "groups": [],
          "user_permissions": []
      }
  },
  {
      "model": "authtoken.token",
      "pk": "7e033a902ac180e0231fced74ba58fcd658c7bbf",
      "fields": {
          "user": 1,
          "created": "2021-07-30T00:00:27.143Z"
      }
  },
  {
      "model": "diary.post",
      "pk": 1,
      "fields": {
          "user": 1,
          "title": "1つめの日記",
          "body": "1つめの日記本文",
          "created_at": "2021-07-03T05:10:40.097Z",
          "updated_at": "2021-07-22T14:22:57.007Z",
          "categories": [
              1
          ]
      }
  },
  {
      "model": "diary.post",
      "pk": 2,
      "fields": {
          "user": 1,
          "title": "2つめの日記",
          "body": "2つめの日記本文",
          "created_at": "2021-07-04T05:11:13.196Z",
          "updated_at": "2021-07-22T13:59:50.294Z",
          "categories": [
              2
          ]
      }
  },
  {
      "model": "diary.category",
      "pk": 1,
      "fields": {
          "name": "日記",
          "slug": "diary",
          "created_at": "2021-07-22T13:59:23.525Z",
          "updated_at": "2021-07-22T13:59:23.525Z"
      }
  },
  {
      "model": "diary.category",
      "pk": 2,
      "fields": {
          "name": "雑記",
          "slug": "general",
          "created_at": "2021-07-22T13:59:39.153Z",
          "updated_at": "2021-07-22T13:59:39.153Z"
      }
  }
]

日記データを予め入れておくのに加え、

  {
      "model": "authtoken.token",
      "pk": "7e033a902ac180e0231fced74ba58fcd658c7bbf",
      "fields": {
          "user": 1,
          "created": "2021-07-30T00:00:27.143Z"
      }
  },

↑でトークン認証のトークンも作っておいた。

テストでAPIをコールする際は、ヘッダに7e033a902ac180e0231fced74ba58fcd658c7bbfをつければ認証できる。

トークン認証を使ったAPIテスト

早速Token認証のテストをやってみる。

tests.pyに下記を書いてみる。

from django.test import TestCase, Client
from diary.models import User, Post
from django.urls import reverse

# Create your tests here.
class TestApiPosts(TestCase):
  '''投稿のAPIテスト'''
  # 1. fixtureを読み込む
  fixtures = ['diary/fixtures/test/test_api_posts.json']

  def test_1(self):
    '''投稿一覧が取得できるか'''
    client = Client()
    response = client.get(
      # 2. urlを指定
      '/api/posts/',
      # 3. ヘッダに認証トークンを付与する
      HTTP_AUTHORIZATION=f"Token 7e033a902ac180e0231fced74ba58fcd658c7bbf"
    )
    self.assertEqual(200, response.status_code)

やることは下記。

  1. fixtureを読み込む
  2. 投稿一覧のurlを指定
  3. ヘッダに認証トークンを付与する

の3つだけ。

「3. ヘッダに認証トークンを付与する」をやらないと、認証エラーで401になるはずなので注意だ。

では、テストを実行してみる。

$ python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.
----------------------------------------------------------------------
Ran 1 test in 0.034s

OK
わいへい
わいへい

トークン認証でもテストできた!

ログイン認証を使ったAPIテスト

次はトークン認証ではなく、ログイン認証でAPIを叩いてみる。

さっきやった、ブラウザでログインしてからAPIを叩くやつだ。

テストコードを下記のように書き換える。

from django.test import TestCase, Client
from diary.models import User, Post
from django.urls import reverse

# Create your tests here.
class TestApiPosts(TestCase):
  '''投稿のAPIテスト'''
  # 1. fixtureを読み込む
  fixtures = ['diary/fixtures/test/test_api_posts.json']

  def test_1(self):
    '''投稿一覧が取得できるか'''
    client = Client()
    # 2. ユーザーIDが1のユーザーでログインする
    client.force_login(User.objects.get(id=1))
    response = client.get(
      # 3. urlを指定
      '/api/posts/',
    )
    self.assertEqual(200, response.status_code)

やることは下記。

  1. fixtureを読み込む
  2. ユーザーIDが1のユーザーでログインする
  3. urlを指定

の3つだけ。

force_login関数を使うと、そのclientのセッション中、指定したユーザーでログインした状態になる。

パスワードも必要ないので便利だ。

では、テストを実行してみる。

$ python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.
----------------------------------------------------------------------
Ran 1 test in 0.034s

OK
わいへい
わいへい

これでログイン認証でもトークン認証でもAPIのテストができるようになったね

レスポンスのテスト

認証できるようになったので、いよいよレスポンスのテストを書いてみよう。

例えば投稿一覧が取得できるかのテストを書いてみると、こういう感じになる。

  def test_1(self):
    '''投稿一覧が取得できるか'''
    client = Client()
    response = client.get(
      '/api/posts/',
      HTTP_AUTHORIZATION=f"Token 7e033a902ac180e0231fced74ba58fcd658c7bbf"
    )
    self.assertEqual(200, response.status_code)
    self.assertEqual(
      [
        {
          "id": 1,
          "user": {
            "id": 1,
            "username": "yhei",
            "email": "yheihei0126@gmail.com"
          },
          "title": "1つめの日記",
          "body": "1つめの日記本文",
          "created_at": "2021-07-03T05:10:40.097000Z",
          "updated_at": "2021-07-22T14:22:57.007000Z",
          "categories": [
            {
              "name": "日記",
              "slug": "diary"
            }
          ]
        },
        {
          "id": 2,
          "user": {
            "id": 1,
            "username": "yhei",
            "email": "yheihei0126@gmail.com"
          },
          "title": "2つめの日記",
          "body": "2つめの日記本文",
          "created_at": "2021-07-04T05:11:13.196000Z",
          "updated_at": "2021-07-22T13:59:50.294000Z",
          "categories": [
            {
              "name": "雑記",
              "slug": "general"
            }
          ]
        },
      ],
      response.json()
    )

response.json()に投稿一覧のレスポンスがdictのリストで入ってくる。

fixturesで入れた日記データが、きちんと所定のフォーマットで返ってきているかを

self.assertEqual(期待値, response.json())

で確認してやればいいわけだ。

クエリパラメータをつけたテスト

説明していなかったが、投稿一覧のURLにクエリパラメータをつけることで、カテゴリーでの検索が可能だ。

下記のURLにログインした状態でアクセスすると、

http://127.0.0.1:8000/api/posts/?category=diary

こんな感じに、slugが「diary」のカテゴリーを持つ記事だけが取得できる。

つーことで、カテゴリーの検索機能のテストコードを書いてみよう。

こんな感じだ。

  def test_2(self):
    '''カテゴリで検索できるか'''
    client = Client()
    response = client.get(
      '/api/posts/',
      {
        'category': 'diary',
      },
      HTTP_AUTHORIZATION=f"Token 7e033a902ac180e0231fced74ba58fcd658c7bbf"
    )
    self.assertEqual(200, response.status_code)
    self.assertEqual(
      [
        {
          "id": 1,
          "user": {
            "id": 1,
            "username": "yhei",
            "email": "yheihei0126@gmail.com"
          },
          "title": "1つめの日記",
          "body": "1つめの日記本文",
          "created_at": "2021-07-03T05:10:40.097000Z",
          "updated_at": "2021-07-22T14:22:57.007000Z",
          "categories": [
            {
              "name": "日記",
              "slug": "diary"
            }
          ]
        },
      ],
      response.json()
    )

クエリパラメータは、下記のようにClientクラスのgetメソッドの第二引数に辞書型を入れてやれば指定できる。

    response = client.get(
      '/api/posts/',  # URL指定
      {
        'category': 'diary',  # ?category=diary のクエリを追加
      },
      HTTP_AUTHORIZATION=f"Token 7e033a902ac180e0231fced74ba58fcd658c7bbf"
    )

次に期待するレスポンスが得られたかを確認する。

response.json()のアサートで、slugがdiaryの投稿だけを指定してやる。

    self.assertEqual(
      [
        {
          "id": 1,
          "user": {
            "id": 1,
            "username": "yhei",
            "email": "yheihei0126@gmail.com"
          },
          "title": "1つめの日記",
          "body": "1つめの日記本文",
          "created_at": "2021-07-03T05:10:40.097000Z",
          "updated_at": "2021-07-22T14:22:57.007000Z",
          "categories": [
            {
              "name": "日記",
              "slug": "diary"
            }
          ]
        },
      ],
      response.json()
    )

これでOK。

テストも大丈夫そうだ。

$ python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
..
----------------------------------------------------------------------
Ran 2 tests in 0.044s

OK

新規投稿作成のテスト(POST)

今までGETのテストばっかりやってきたが、ここいらでPOSTのテストもやってみよう。

新規投稿のAPIがちょうどPOSTを扱っている。

methodエンドポイント名前
GET/api/posts/投稿一覧取得
POST/api/posts/新規投稿
GET/api/posts/:id/投稿取得
PUT/api/posts/:id/投稿更新
PATCH/api/posts/:id/投稿一部更新
DELETE/api/posts/:id/投稿削除

新規投稿のエンドポイントは

  • /api/posts/
  • POSTメソッドで
  • titlebodyをリクエストボディーにjsonでつめてやると
  • 認証したユーザーを投稿者として、新規の記事を作ってくれる

APIだ。

これらが実行できるか、テストコードで検証してみよう。

こんな感じだ。

  def test_5(self):
    '''新規投稿できるか'''
    client = Client()
    # 1. postメソッドでPOSTできる
    response = client.post(
      '/api/posts/',
      # 2. 第二引数に辞書型を入れると、リクエストボディーに指定する値が定義される
      {
        'title': 'title for test_6',
        'body': 'body for test_6',
      },
      content_type='application/json',
      HTTP_AUTHORIZATION=f"Token 7e033a902ac180e0231fced74ba58fcd658c7bbf"
    )
    self.assertEqual(201, response.status_code)
    post = Post.objects.get(title='title for test_6')
    self.assertEqual('body for test_6', post.body)
    self.assertEqual(User.objects.first(), post.user)

テスト実行は省略するが、各自やってみてくれ。

残りのテストを完成させてみよう

ウサギ
ウサギ

OK!

ここまででAPIのテストは一通りできるようになった

ウサギ
ウサギ

あとは残りの機能のテストを自分の手で完成させてみようか

methodエンドポイント名前どんな動作か
GET/api/posts/投稿一覧取得・(済)全記事のリストを取得できる
・タイトルと本文をキーワード検索できる
・更新時刻、作成時刻で並び替えができる
(済)カテゴリで検索できる
POST/api/posts/新規投稿(済)タイトルと本文を指定して記事を投稿できる
GET/api/posts/:id/投稿取得・特定の記事のタイトル、本文、カテゴリ、投稿者情報を取得できる
PUT/api/posts/:id/投稿更新・特定の記事を指定したタイトル、本文で更新できる
PATCH/api/posts/:id/投稿一部更新・特定の記事を、タイトルだけ、本文だけ、など一部のフィールドだけ更新できる
DELETE/api/posts/:id/投稿削除・特定の記事を削除できる
投稿に関するAPI仕様

今までのテストで基本は網羅できた。↑の仕様動作のうち、まだ実装していないテストを自分でやってみよう。

答えは下記にあるが、まずは試行錯誤してみてくれ。

テストコード解答

APIの自動テスト方法まとめ

ウサギ
ウサギ

今回は、APIのテスト方法を学んだな

トークン認証は下記のようにヘッダを指定することで可能だ。

    response = client.get(
      '/api/posts/',
      # ヘッダに認証トークンを付与する
      HTTP_AUTHORIZATION=f"Token 7e033a902ac180e0231fced74ba58fcd658c7bbf"
    )

ログイン認証はforce_login関数を使うことで実現できる。

    # ユーザーIDが1のユーザーでログインする
    client.force_login(User.objects.get(id=1))
    response = client.get(
      # urlを指定
      '/api/posts/',
    )

GETでクエリストリングを指定するのは下記。

    response = client.get(
      '/api/posts/',
      {
        'category': 'diary',
      },
      HTTP_AUTHORIZATION=f"Token 7e033a902ac180e0231fced74ba58fcd658c7bbf"
    )

POSTでリクエストボディにパラメータ指定をするのは下記だ。

    response = client.post(
      '/api/posts/',
      {
        'title': 'title for test_6',
        'body': 'body for test_6',
      },
      content_type='application/json',
      HTTP_AUTHORIZATION=f"Token 7e033a902ac180e0231fced74ba58fcd658c7bbf"
    )

PUTやPATCH、DELETEももちろん可能だ。それは自分で調べてみてくれ。

わいへい
わいへい

これでテストコードの書き方はだいたい終わったのかな?

ウサギ
ウサギ

もう一つ、よく使うJWT認証でテストを行う方法を次回やっていくぞ

わいへい
わいへい

JWT認証?????

ウサギ
ウサギ

JWT認証ってなに? って顔してるな

\ 講座で学んだことを即アウトプットしよう /

次の講座

Djangoの基礎を学びたい方は

Djangoの基礎を固めたい方はこちら(セール時に買うのがおすすめ)

『Djangoパーフェクトマスター』〜インスタ映えを支えるPython超高速開発Webフレームワークを徹底解説!

動画講座で手を動かしながら、ほとんどのことが学べます。

ウサギもここから始めました。

コメント

  1. […] APIを自動テストするDjango自動テスト講座第二弾! 関数やクラスをテストす… ウサギ […]

タイトルとURLをコピーしました