0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

pytest による django の Web API のテスト自動化

Posted at

やりたいこと

django の Web API のレスポンスが期待通りかを自動でテストする。
テストには pytest を使用する。

django の Web API 実装例

ここでは以下の Item の生成 (create)、一覧 (list) などの操作を行える API を用意する。

app1/models.py
class Item(models.Model):
    name = models.CharField(max_length=32)
    is_deleted = models.BooleanField(default=False)

ファイル構成

pytest1
├── README.txt
├── app1
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── migrations
│   │   ├── 0001_initial.py
│   │    ...
│   ├── services.py
│   └── views.py
├── manage.py
├── pytest1
│   ├── __init__.py
│   ├── asgi.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
└── schema.yml

API 例

  • POST /app1/create/?name={name}
  • GET /app1/list/

API 実装例

app1/views.py
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
from rest_framework import status

from app1.services import (
    create,
    list,
)

@csrf_exempt
@require_http_methods(["POST"])
def api_create(request):
    response = {
        'data': None,
    }

    name = request.GET.get('name')
    item = create(name)
    response['data'] = to_json(item)
    return JsonResponse(response)

@csrf_exempt
@require_http_methods(["GET"])
def api_list(request):
    response = {
        'data': None,
    }

    items = list()
    response['data'] = to_json_list(items)
    return JsonResponse(response)

app1/services.py

app1/services.py
from app1.models import Item

def create(name):
    item = Item(
        name=name,
    )
    item.save()
    return item

def list():
    items = Item.objects.filter(
        is_deleted=False
    ).order_by("id")
    return items

pytest

パッケージのインストール

$ pip install pytest
$ pip install pytest-django

テスト用のファイル構成

pytest1
├── app1
│   └── test_apis
│        ├── __init__.py
│        ├── test_api_create.py
│        └── test_api_list.py
├── conftest.py
└── pytest.ini

conftest.py

conftest.py には複数のテストで使う共通的な処理を定義する。
@pytest.fixture デコレータを付与することで、テストメソッドで使用することができる。

conftest.py
import pytest
from rest_framework.test import APIClient

@pytest.fixture
def api_client():
    client = APIClient()
    return client

pytest.ini

pytest のカスタマイズのための設定ファイル。

pytest.ini
[pytest]
env_override_existing_values=1
env_files=
    .env

DJANGO_SETTINGS_MODULE=pytest1.settings

テストプログラム

ここでは app1/views.py に定義した API に対して、app1/test_apis/ ディレクトリに API 毎にテストファイルを作成している。

  • app1/views.py: API の定義
  • app1/test_api_create.py: /app1/create/ のテスト
  • app1/test_api_list.py: /app1/list/ のテスト

test_api_create.py

app1/test_apis/test_api_create.py
import pytest

from rest_framework import status
from rest_framework.response import Response
from rest_framework.test import APIClient

from app1.models import Item


class TestApiCreate:

    @pytest.mark.django_db
    def test_basic(self, api_client):
        api_client.raise_request_exception = True
        # api_client.force_authenticate(user=test_user)

        # request
        name = 'item_01'
        url = f'/app1/create/?name={name}'
        response = api_client.post(url)

        # レスポンスのステータスコードを確認
        assert response.status_code == status.HTTP_201_CREATED

        # 
        response_obj = response.json()
        print(response_obj)
        assert response_obj['data']['name'] == name

        # database
        item = Item.objects.get(id=response_obj['data']['id'])
        assert item
        assert response_obj['data']['name'] == item.name

test_api_list.py

app1/test_apis/test_api_list.py
import pytest

from rest_framework import status
from rest_framework.response import Response
from rest_framework.test import APIClient

from app1.models import Item


class TestApiCreate:

    @pytest.mark.django_db
    def test_basic(self, api_client):
        api_client.raise_request_exception = True

        # Item オブジェクトを作成
        items = []
        for i in range(1, 4):
            name = f"item_{i:02d}"
            item = Item.objects.create(
                name=name,
                is_deleted=False,
            )
            items.append(item)

        # API にリクエストを送信
        url = f'/app1/list/'
        response = api_client.get(url)

        # レスポンスの HTTP ステータスコードを確認 
        assert response.status_code == status.HTTP_200_OK

        # レスポンスのデータを確認
        response_obj = response.json()
        print(response_obj)

        for item in items:
            obj = next(
                (obj for obj in response_obj['data'] if obj['id'] == item.id),
                None
            )
            # id が一致するオブジェクトが取得できていることを確認
            assert obj
            # 該当のオブジェクトの name が一致することを確認
            assert obj['name'] == item.name

pytest 実行例

pytest の実行方法

テストファイルが格納されているディレクトリまたはファイル名を指定して pytest を実行することができる。

ディレクトリ指定の場合
$ python app1/test_apis
ファイル指定の場合
$ python app1/test_apis/test_api_create.py

pytest 実行結果例

$ pytest app1/test_apis
============================= test session starts ==============================
platform linux -- Python 3.12.5, pytest-9.0.2, pluggy-1.6.0
django: version: 5.1.6, settings: pytest1.settings (from ini)
rootdir: .../pytest1
configfile: pytest.ini
plugins: dotenv-0.5.2, django-4.11.1
collected 2 items

app1/test_apis/test_api_create.py .                                      [ 50%]
app1/test_apis/test_api_list.py .                                        [100%]

============================== 2 passed in 0.93s ===============================

最下行に 2 passed が出力されており、2つのテストが成功したことがわかる。

-s を指定すると stdout の出力内容を確認することができる。

$ pytest app1/test_apis -s
============================= test session starts ==============================
platform linux -- Python 3.12.5, pytest-9.0.2, pluggy-1.6.0
django: version: 5.1.6, settings: pytest1.settings (from ini)
rootdir: .../pytest1
configfile: pytest.ini
plugins: dotenv-0.5.2, django-4.11.1
collected 2 items

app1/test_apis/test_api_create.py {'data': {'id': 1, 'name': 'item_01', 'is_deleted': False}}
.
app1/test_apis/test_api_list.py {'data': [{'id': 2, 'name': 'item_01', 'is_deleted': False}, {'id': 3, 'name': 'item_02', 'is_deleted': False}, {'id': 4, 'name': 'item_03', 'is_deleted': False}]}
.

============================== 2 passed in 0.86s ===============================
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?