Djangoテスト完全ガイド
Djangoテスト完全ガイド
Section titled “Djangoテスト完全ガイド”Djangoアプリケーションのテストを、実務で使える実装例とともに詳しく解説します。
1. テストとは
Section titled “1. テストとは”テストの重要性
Section titled “テストの重要性”テストは、アプリケーションの品質を保証し、リファクタリングを安全に行うために不可欠です。
テストの種類 ├─ 単体テスト(Unit Test) ├─ 統合テスト(Integration Test) └─ E2Eテスト(End-to-End Test)2. Djangoのテストフレームワーク
Section titled “2. Djangoのテストフレームワーク”基本的なテスト
Section titled “基本的なテスト”from django.test import TestCasefrom django.contrib.auth.models import Userfrom .models import Post
class PostModelTest(TestCase): def setUp(self): """テストの前準備""" self.user = User.objects.create_user( username='testuser', email='test@example.com', password='testpass123' ) self.post = Post.objects.create( title='Test Post', content='Test Content', author=self.user )
def test_post_creation(self): """投稿の作成テスト""" self.assertEqual(self.post.title, 'Test Post') self.assertEqual(self.post.author, self.user)
def test_post_str(self): """投稿の文字列表現テスト""" self.assertEqual(str(self.post), 'Test Post')テストの実行
Section titled “テストの実行”# すべてのテストを実行python manage.py test
# 特定のアプリのテストを実行python manage.py test blog
# 特定のテストクラスを実行python manage.py test blog.tests.PostModelTest
# 特定のテストメソッドを実行python manage.py test blog.tests.PostModelTest.test_post_creation3. モデルのテスト
Section titled “3. モデルのテスト”モデルのテスト例
Section titled “モデルのテスト例”from django.test import TestCasefrom django.core.exceptions import ValidationErrorfrom .models import Post, Comment
class PostModelTest(TestCase): def setUp(self): self.user = User.objects.create_user( username='testuser', password='testpass123' )
def test_post_creation(self): """投稿の作成テスト""" post = Post.objects.create( title='Test Post', content='Test Content', author=self.user ) self.assertEqual(Post.objects.count(), 1) self.assertEqual(post.title, 'Test Post')
def test_post_slug_generation(self): """スラッグの自動生成テスト""" post = Post.objects.create( title='Test Post', content='Test Content', author=self.user ) self.assertEqual(post.slug, 'test-post')
def test_post_published_status(self): """公開状態のテスト""" post = Post.objects.create( title='Test Post', content='Test Content', author=self.user, is_published=True ) self.assertTrue(post.is_published)4. ビューのテスト
Section titled “4. ビューのテスト”関数ベースビューのテスト
Section titled “関数ベースビューのテスト”from django.test import TestCase, Clientfrom django.urls import reversefrom django.contrib.auth.models import Userfrom .models import Post
class PostViewTest(TestCase): def setUp(self): self.client = Client() self.user = User.objects.create_user( username='testuser', password='testpass123' ) self.post = Post.objects.create( title='Test Post', content='Test Content', author=self.user )
def test_post_list_view(self): """投稿一覧ビューのテスト""" response = self.client.get(reverse('post_list')) self.assertEqual(response.status_code, 200) self.assertContains(response, 'Test Post')
def test_post_detail_view(self): """投稿詳細ビューのテスト""" response = self.client.get(reverse('post_detail', args=[self.post.pk])) self.assertEqual(response.status_code, 200) self.assertContains(response, 'Test Post') self.assertContains(response, 'Test Content')
def test_post_create_view_requires_login(self): """投稿作成ビューはログインが必要""" response = self.client.get(reverse('post_create')) self.assertEqual(response.status_code, 302) # リダイレクト
def test_post_create_view_with_login(self): """ログイン後の投稿作成テスト""" self.client.login(username='testuser', password='testpass123') response = self.client.get(reverse('post_create')) self.assertEqual(response.status_code, 200)クラスベースビューのテスト
Section titled “クラスベースビューのテスト”from django.test import TestCase, Clientfrom django.urls import reversefrom django.contrib.auth.models import Userfrom .models import Post
class PostListViewTest(TestCase): def setUp(self): self.client = Client() self.user = User.objects.create_user( username='testuser', password='testpass123' ) self.post = Post.objects.create( title='Test Post', content='Test Content', author=self.user )
def test_get_queryset(self): """クエリセットのテスト""" response = self.client.get(reverse('post_list')) self.assertEqual(response.status_code, 200) self.assertQuerysetEqual( response.context['post_list'], ['<Post: Test Post>'] )5. フォームのテスト
Section titled “5. フォームのテスト”フォームのテスト例
Section titled “フォームのテスト例”from django.test import TestCasefrom .forms import PostFormfrom .models import Postfrom django.contrib.auth.models import User
class PostFormTest(TestCase): def setUp(self): self.user = User.objects.create_user( username='testuser', password='testpass123' )
def test_valid_form(self): """有効なフォームのテスト""" form_data = { 'title': 'Test Post', 'content': 'Test Content' } form = PostForm(data=form_data) self.assertTrue(form.is_valid())
def test_invalid_form(self): """無効なフォームのテスト""" form_data = { 'title': '', # 必須フィールドが空 'content': 'Test Content' } form = PostForm(data=form_data) self.assertFalse(form.is_valid()) self.assertIn('title', form.errors)6. APIのテスト
Section titled “6. APIのテスト”Django REST Frameworkのテスト
Section titled “Django REST Frameworkのテスト”from rest_framework.test import APITestCasefrom rest_framework import statusfrom django.contrib.auth.models import Userfrom .models import Post
class PostAPITest(APITestCase): def setUp(self): self.user = User.objects.create_user( username='testuser', password='testpass123' ) self.post = Post.objects.create( title='Test Post', content='Test Content', author=self.user )
def test_get_post_list(self): """投稿一覧の取得テスト""" url = reverse('api:post-list') response = self.client.get(url) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(len(response.data), 1)
def test_create_post(self): """投稿の作成テスト""" self.client.force_authenticate(user=self.user) url = reverse('api:post-list') data = { 'title': 'New Post', 'content': 'New Content' } response = self.client.post(url, data, format='json') self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(Post.objects.count(), 2)
def test_update_post(self): """投稿の更新テスト""" self.client.force_authenticate(user=self.user) url = reverse('api:post-detail', args=[self.post.pk]) data = { 'title': 'Updated Post', 'content': 'Updated Content' } response = self.client.patch(url, data, format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) self.post.refresh_from_db() self.assertEqual(self.post.title, 'Updated Post')7. フィクスチャの使用
Section titled “7. フィクスチャの使用”フィクスチャの作成
Section titled “フィクスチャの作成”# フィクスチャの作成python manage.py dumpdata blog.Post --indent 2 > fixtures/posts.jsonフィクスチャの使用
Section titled “フィクスチャの使用”from django.test import TestCase
class PostTest(TestCase): fixtures = ['posts.json', 'users.json']
def test_post_count(self): """フィクスチャからのデータ読み込みテスト""" self.assertEqual(Post.objects.count(), 10)8. モックの使用
Section titled “8. モックの使用”unittest.mockの使用
Section titled “unittest.mockの使用”from unittest.mock import patch, MagicMockfrom django.test import TestCasefrom django.urls import reverse
class PostViewTest(TestCase): @patch('blog.views.send_email') def test_post_creation_sends_email(self, mock_send_email): """投稿作成時にメールが送信されるテスト""" mock_send_email.return_value = True response = self.client.post(reverse('post_create'), { 'title': 'Test Post', 'content': 'Test Content' }) self.assertTrue(mock_send_email.called)9. カバレッジの測定
Section titled “9. カバレッジの測定”coverageの使用
Section titled “coverageの使用”# coverageのインストールpip install coverage
# テストの実行とカバレッジの測定coverage run --source='.' manage.py test
# カバレッジレポートの表示coverage report
# HTMLレポートの生成coverage html10. 実務でのベストプラクティス
Section titled “10. 実務でのベストプラクティス”パターン1: テストの構造化
Section titled “パターン1: テストの構造化”# ├── __init__.py# ├── test_models.py# ├── test_views.py# ├── test_forms.py# ├── test_api.py# └── factories.py
# factories.py(Factory Boyを使用)import factoryfrom django.contrib.auth.models import Userfrom .models import Post
class UserFactory(factory.django.DjangoModelFactory): class Meta: model = User
username = factory.Sequence(lambda n: f'user{n}') email = factory.LazyAttribute(lambda obj: f'{obj.username}@example.com')
class PostFactory(factory.django.DjangoModelFactory): class Meta: model = Post
title = factory.Sequence(lambda n: f'Post {n}') content = factory.Faker('text') author = factory.SubFactory(UserFactory)パターン2: テストの実行設定
Section titled “パターン2: テストの実行設定”from .base import *
# テスト用のデータベース設定DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': ':memory:' }}
# パスワードハッシャーの高速化PASSWORD_HASHERS = [ 'django.contrib.auth.hashers.MD5PasswordHasher',]11. よくある問題と解決策
Section titled “11. よくある問題と解決策”問題1: テストが遅い
Section titled “問題1: テストが遅い”原因:
- データベースへのアクセスが多い
- フィクスチャが大きい
解決策:
# メモリ内データベースの使用DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': ':memory:' }}
# トランザクションの使用from django.test import TransactionTestCase
class FastTest(TransactionTestCase): # トランザクションを有効化 pass問題2: テストが失敗する
Section titled “問題2: テストが失敗する”原因:
- テストデータの準備が不十分
- アサーションが間違っている
解決策:
# デバッグ情報の出力def test_something(self): response = self.client.get('/') print(response.content) # レスポンス内容を確認 print(response.status_code) # ステータスコードを確認 self.assertEqual(response.status_code, 200)これで、Djangoのテストの基礎知識と実務での使い方を理解できるようになりました。