LoginSignup
0
0

[Django]Viewのテストを作成してみる

Last updated at Posted at 2024-05-03

はじめに

自分用の個人開発のメモ/備忘録として記録していきます。

環境

django5.0
↓ dockerでの開発環境作成はこちら

↓ 認証ユーザーモデルはこちらをそのまま利用

やりたいこと

Viewのテストを一通り書いてみる

前回

テストについて下調べ

おもに下記のようなことを考えてる必要がある模様。

No カテゴリ 内容
1 アクセス制御テスト 適切なユーザーがリソースにアクセスでき、不適切なユーザーがアクセスできないことを確認。例えば、認証されたユーザーのみが特定のエンドポイントを利用できるか、また特定のロールを持つユーザーのみがリソースを編集または削除できるかなど
2 入力検証テスト APIが受け取る入力(POSTやPUTリクエストで送信されるデータ)が適切に検証されていることを確認。無効なデータが拒否され、適切なエラーメッセージが返されることをテスト
3 機能テスト 各APIエンドポイントが期待通りに機能するかを確認。リソースの作成、取得、更新、削除など
4 エラーハンドリングテスト APIがエラーを適切に処理し、適切なHTTPステータスコードとエラーメッセージを返すことを確認。例えば、存在しないリソースへのアクセスや無効なアクションの実行など
5 パフォーマンステスト APIが大量のリクエストや大きなデータセットを効率的に処理できるかを確認。大規模なアプリケーションや高負荷環境では重要
6 セキュリティテスト APIがSQLインジェクション、クロスサイトスクリプティング(XSS)、クロスサイトリクエストフォージェリ(CSRF)などの一般的な脆弱性から保護されていることを確認

準備

とりあえずViewを準備

・user に紐づいた project に対するViewSetを作成
・userはカスタムユーザーを利用

views.py
from rest_framework import viewsets
from .models import Project
from .serializers import ProjectSerializer

class ProjectViewSet(viewsets.ModelViewSet):
    queryset = Project.objects.all()
    serializer_class = ProjectSerializer

    def get_queryset(self):
        return Project.objects.filter(user=self.request.user)

    def get_serializer_class(self):
        if self.action == 'retrieve':
            return ProjectDetailSerializer
        return ProjectSerializer

test_views.pyを書いていく

ModelViewSetを使っているため、基本的にはテストは省略してもよいようなのですが、慣れるためにテストを作成する。

APITestCaseを継承

DRFテストは Django の TestCase を拡張した APITestCase を利用して行う。APIClient を使用して HTTP リクエストをシミュレートし、レスポンスを評価できる。

test_views.py
from django.urls import reverse
from rest_framework import status
from rest_framework.test import APITestCase
from django.contrib.auth import get_user_model
from tms.models import Project, List, Ticket, Comment

test_email = 'testuser@test.com'
test_password = 'testpass'
User = get_user_model()

class ProjectViewSetTestCase(APITestCase):

テストの前処理をsetUp()に記載
認証はsimplejwtを利用しているので、トークンを取得する処理を追加。

test_views.py
    def setUp(self):
        self.user = User.objects.create_user(email=test_email, password=test_password)
        response = self.client.post(reverse('token_obtain_pair'), {'email': test_email, 'password': test_password})
        self.token = response.data['access']
        self.client.credentials(HTTP_AUTHORIZATION=f'Bearer {self.token}')
        self.project = Project.objects.create(name="Test Project", user=self.user)
        self.url = reverse('project-list')

listのテスト

・ステータスと、データ数の確認

test_views.py
def test_project_list(self):
    response = self.client.get(self.url)
    self.assertEqual(response.status_code, status.HTTP_200_OK)
    self.assertEqual(len(response.data), 1)

createのテスト

・ステータスと、データ数の確認
・name/descriptionの一致確認

test_views.py
def test_project_create(self):
    response = self.client.post(self.url, {'name': 'New Project', 'description': 'New Description'})
    self.assertEqual(response.status_code, status.HTTP_201_CREATED)
    self.assertEqual(Project.objects.count(), 2)
    self.assertEqual(Project.objects.last().name, 'New Project')
    self.assertEqual(Project.objects.last().description, 'New Description')

retrieveのテスト

・ステータス、データ数の確認
・user > project > list > ticket とリレーションしているデータをそれぞれテスト

test_views.py
def test_project_retrieve(self):
    self.list = List.objects.create(title="Test List", project=self.project)
    self.ticket = Ticket.objects.create(title="Test Ticket", list=self.list)
    url = reverse('project-detail', args=[self.project.id])
    response = self.client.get(url)
    self.assertEqual(response.status_code, status.HTTP_200_OK)
    self.assertEqual(response.data['name'], 'Test Project')
    self.assertEqual(len(response.data['lists']), 1)
    self.assertEqual(response.data['lists'][0]['title'], 'Test List')
    self.assertEqual(len(response.data['lists'][0]['tickets']), 1)
    self.assertEqual(response.data['lists'][0]['tickets'][0]['title'], 'Test Ticket')

updateのテスト

・ステータスと、更新後の値の一致確認

test_views.py
def test_project_update(self):
    url = reverse('project-detail', args=[self.project.id])
    response = self.client.patch(url, {'name': 'Updated Name'})
    self.assertEqual(response.status_code, status.HTTP_200_OK)
    response = self.client.get(url)
    self.assertEqual(response.data['name'], 'Updated Name')

deleteのテスト

・ステータス確認

test_views.py
def test_project_delete(self):
    url = reverse('project-detail', args=[self.project.id])
    response = self.client.delete(url)
    self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)

unauthrizedのテスト

・setUpで作成したユーザーとは別のユーザーを作成
・作成したユーザー以外は取得・更新・削除など出来ないことを確認
(querysetでfilterをかけているため404になる)

test_views.py
def test_project_unauthrized(self):
    self.user = User.objects.create_user(email='unauthrized@test.com', password='unauthrized')
    response = self.client.post(reverse('token_obtain_pair'), {'email': 'unauthrized@test.com', 'password': 'unauthrized'})
    self.token = response.data['access']
    self.client.credentials(HTTP_AUTHORIZATION=f'Bearer {self.token}')
    url = reverse('project-detail', args=[self.project.id])
    response = self.client.get(url)
    self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
    response = self.client.patch(url, {'name': 'Updated Name'})
    self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
    response = self.client.delete(url)
    self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

test実行

テスト実行

python manage.py test

全てのテストが成功すればこのような表示となる

Found 19 test(s).
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
...................
----------------------------------------------------------------------
Ran 19 tests in 4.555s

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

おわりに

・仕様通りの返却がされるか、テストを作成するのは非常に重要
・パフォーマンスやセキュリティのテストなど、もっと調べる必要がありそう

参考

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