質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
Django

DjangoはPythonで書かれた、オープンソースウェブアプリケーションのフレームワークです。複雑なデータベースを扱うウェブサイトを開発する際に必要な労力を減らす為にデザインされました。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

1回答

1866閲覧

Djangoでのテストが成功しない

yusuke22

総合スコア8

Django

DjangoはPythonで書かれた、オープンソースウェブアプリケーションのフレームワークです。複雑なデータベースを扱うウェブサイトを開発する際に必要な労力を減らす為にデザインされました。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

0グッド

1クリップ

投稿2021/10/25 08:04

編集2021/10/25 17:21

前提・実現したいこと

Djangoにてテストを特定し修正を行いたいです。

ここに質問の内容を詳しく書いてください。
下記書籍にてDjangoの勉強をしております。

■動かして学ぶ! Python Django開発入門
https://amzn.to/3vdlf5M

その中でテストを実行したのですが下記のエラーが出力されてしまい成功しません。
またサンプルのソースコードと見比べても間違いがないため、どこを修正すべきかわからず質問させていただきました。

発生している問題・エラーメッセージ

ERROR: test_delete_diary_failure (diary.tests.test_views.TestDiaryDeleteView) 日記削除処理が失敗することを検証する ---------------------------------------------------------------------- Traceback (most recent call last): File "/Users/name/venv_private_diary/bin/private_diary/diary/tests/test_views.py", line 113, in test_delete_diary_failure response = self.client.post(reverse_lazy('diary:diary_delete', kwargs={'pk': 999})) File "/Users/name/venv_private_diary/lib/python3.7/site-packages/django/test/client.py", line 543, in post response = super().post(path, data=data, content_type=content_type, secure=secure, **extra) File "/Users/name/venv_private_diary/lib/python3.7/site-packages/django/test/client.py", line 357, in post secure=secure, **extra) File "/Users/name/venv_private_diary/lib/python3.7/site-packages/django/test/client.py", line 422, in generic return self.request(**r) File "/Users/name/venv_private_diary/lib/python3.7/site-packages/django/test/client.py", line 503, in request raise exc_value File "/Users/name/venv_private_diary/lib/python3.7/site-packages/django/core/handlers/exception.py", line 34, in inner response = get_response(request) File "/Users/name/venv_private_diary/lib/python3.7/site-packages/django/core/handlers/base.py", line 115, in _get_response response = self.process_exception_by_middleware(e, request) File "/Users/name/venv_private_diary/lib/python3.7/site-packages/django/core/handlers/base.py", line 113, in _get_response response = wrapped_callback(request, *callback_args, **callback_kwargs) File "/Users/name/venv_private_diary/lib/python3.7/site-packages/django/views/generic/base.py", line 71, in view return self.dispatch(request, *args, **kwargs) File "/Users/name/venv_private_diary/lib/python3.7/site-packages/django/contrib/auth/mixins.py", line 52, in dispatch return super().dispatch(request, *args, **kwargs) File "/Users/name/venv_private_diary/lib/python3.7/site-packages/django/contrib/auth/mixins.py", line 106, in dispatch user_test_result = self.get_test_func()() File "/Users/name/venv_private_diary/bin/private_diary/diary/views.py", line 43, in test_func diary = Diary.objects.get(pk=self.kwargs['pk']) File "/Users/name/venv_private_diary/lib/python3.7/site-packages/django/db/models/manager.py", line 82, in manager_method return getattr(self.get_queryset(), name)(*args, **kwargs) File "/Users/name/venv_private_diary/lib/python3.7/site-packages/django/db/models/query.py", line 408, in get self.model._meta.object_name diary.models.Diary.DoesNotExist: Diary matching query does not exist. ====================================================================== ERROR: test_update_diary_failure (diary.tests.test_views.TestDiaryUpdateView) 日記編集処理が失敗することを検証する ---------------------------------------------------------------------- Traceback (most recent call last): File "/Users/name/venv_private_diary/bin/private_diary/diary/tests/test_views.py", line 85, in test_update_diary_failure response = self.client.post(reverse_lazy('diary:diary_update', kwargs={'pk': 999})) File "/Users/name/venv_private_diary/lib/python3.7/site-packages/django/test/client.py", line 543, in post response = super().post(path, data=data, content_type=content_type, secure=secure, **extra) File "/Users/name/venv_private_diary/lib/python3.7/site-packages/django/test/client.py", line 357, in post secure=secure, **extra) File "/Users/name/venv_private_diary/lib/python3.7/site-packages/django/test/client.py", line 422, in generic return self.request(**r) File "/Users/name/venv_private_diary/lib/python3.7/site-packages/django/test/client.py", line 503, in request raise exc_value File "/Users/name/venv_private_diary/lib/python3.7/site-packages/django/core/handlers/exception.py", line 34, in inner response = get_response(request) File "/Users/name/venv_private_diary/lib/python3.7/site-packages/django/core/handlers/base.py", line 115, in _get_response response = self.process_exception_by_middleware(e, request) File "/Users/name/venv_private_diary/lib/python3.7/site-packages/django/core/handlers/base.py", line 113, in _get_response response = wrapped_callback(request, *callback_args, **callback_kwargs) File "/Users/name/venv_private_diary/lib/python3.7/site-packages/django/views/generic/base.py", line 71, in view return self.dispatch(request, *args, **kwargs) File "/Users/name/venv_private_diary/lib/python3.7/site-packages/django/contrib/auth/mixins.py", line 52, in dispatch return super().dispatch(request, *args, **kwargs) File "/Users/name/venv_private_diary/lib/python3.7/site-packages/django/contrib/auth/mixins.py", line 106, in dispatch user_test_result = self.get_test_func()() File "/Users/name/venv_private_diary/bin/private_diary/diary/views.py", line 43, in test_func diary = Diary.objects.get(pk=self.kwargs['pk']) File "/Users/name/venv_private_diary/lib/python3.7/site-packages/django/db/models/manager.py", line 82, in manager_method return getattr(self.get_queryset(), name)(*args, **kwargs) File "/Users/name/venv_private_diary/lib/python3.7/site-packages/django/db/models/query.py", line 408, in get self.model._meta.object_name diary.models.Diary.DoesNotExist: Diary matching query does not exist. ---------------------------------------------------------------------- Ran 7 tests in 4.641s FAILED (errors=2) Destroying test database for alias 'default'... Process finished with exit code 1

該当のソースコード

test_views.py

Python

1from django.contrib.auth import get_user_model 2from django.test import TestCase 3from django.urls import reverse_lazy 4 5from ..models import Diary 6 7(・・略・・) 8 9class TestDiaryUpdateView(LoggedInTestCase): 10 """DiaryUpdateView用のテストクラス""" 11 12(・・略・・) 13 14 def test_update_diary_failure(self):          # テスト失敗箇所 15 """日記編集処理が失敗することを検証する""" 16 17 # 日記編集処理(Post)を実行 18 response = self.client.post(reverse_lazy('diary:diary_update', kwargs={'pk': 999})) 19 20 # 存在しない日記データを編集しようとしてエラーになることを検証 21 self.assertEqual(response.status_code, 404) 22 23 24class TestDiaryDeleteView(LoggedInTestCase): 25 """DiaryDeleteView用のテストクラス""" 26 27(・・略・・) 28 29 def test_delete_diary_failure(self):          # テスト失敗箇所 30 """日記削除処理が失敗することを検証する""" 31 32 # 日記削除処理(Post)を実行 33 response = self.client.post(reverse_lazy('diary:diary_delete', kwargs={'pk': 999})) 34 35 # 存在しない日記データを削除しようとしてエラーになることを検証 36 self.assertEqual(response.status_code, 404)

views.py

Python

1import logging 2 3from django.contrib import messages 4from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin 5from django.urls import reverse_lazy 6from django.views import generic 7 8from .forms import InquiryForm, DiaryCreateForm 9from .models import Diary 10 11logger = logging.getLogger(__name__) 12 13(・・略・・) 14 15class OnlyYouMixin(UserPassesTestMixin): 16 # 以下test_func関数がFalseを返すと、raise_exceptionがTrueの場合は403ページに遷移し、False(デフォルト)の場合はログインページに遷移 17 raise_exception = True 18 19 def test_func(self): 20 # URLに埋め込まれた主キーから日記データを1件取得 21 diary = Diary.objects.get(pk=self.kwargs['pk'])          # 両方のテスト失敗箇所(line 43) 22 # ログインユーザーと日記データの作成ユーザーを比較し、異なればraise_exceptionの設定に従う 23 return self.request.user == diary.user 24 25(・・略・・) 26 27class DiaryUpdateView(LoginRequiredMixin, OnlyYouMixin, generic.UpdateView): 28 model = Diary 29 template_name = 'diary_update.html' 30 form_class = DiaryCreateForm 31 32 def get_success_url(self): 33 return reverse_lazy('diary:diary_detail', kwargs={'pk': self.kwargs['pk']}) 34 35 def form_valid(self, form): 36 messages.success(self.request, "日記を更新しました。") 37 return super().form_valid(form) 38 39 def form_invalid(self, form): 40 messages.error(self.request, "日記の更新に失敗しました。") 41 return super().form_invalid(form) 42 43class DiaryDeleteView(LoginRequiredMixin, OnlyYouMixin, generic.DeleteView): 44 model = Diary 45 template_name = 'diary_delete.html' 46 success_url = reverse_lazy('diary:diary_list') 47 48 def delete(self, request, *args, **kwargs): 49 messages.success(self.request, "日記を削除しました。") 50 return super().delete(request, *args, **kwargs)

ファイル構成
イメージ説明

試したこと

サンプルコードを見比べて誤字を修正しました。

補足情報

macOS Big Sur ver 11.6
Python 3.7.12
Django 2.2.2

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

attakei

2021/10/25 11:46

test_views.pyだけではなくviews.pyも掲載したほうが良いです。 同じ書籍を持っている人がいたら、そのコードを元に本人が気づかない誤字を回答として指摘することも出来ます。 仮に持っていないくても、テスト対象のコードがあれば「◯◯◯なのだからテストに失敗はします」という回答が出てくることもあると思います。(この場合は、書籍の道筋とはずれる可能性はあれど、有益ではあると思います)
yusuke22

2021/10/25 14:34

ご連絡ありがとうございます。 文字数の関係で質問に追記できなかったため、下記にviews.pyのソースコードを追記いたします。 見にくくなり恐縮ですがご確認いただけますと幸いです。 ▼views.py -------------------------------------------------- import logging from django.contrib import messages from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin from django.urls import reverse_lazy from django.views import generic from .forms import InquiryForm, DiaryCreateForm from .models import Diary logger = logging.getLogger(__name__) class IndexView(generic.TemplateView): template_name = "index.html" class InquiryView(generic.FormView): template_name = "inquiry.html" form_class = InquiryForm success_url = reverse_lazy('diary:inquiry') def form_valid(self, form): form.send_email() messages.success(self.request, 'メッセージを送信しました。') logger.info('Inquiry sent by {}'.format(form.cleaned_data['name'])) return super().form_valid(form) class DiaryListView(LoginRequiredMixin, generic.ListView): model = Diary template_name = 'diary_list.html' paginate_by = 2 def get_queryset(self): diaries = Diary.objects.filter(user=self.request.user).order_by('-created_at') return diaries class OnlyYouMixin(UserPassesTestMixin): # 以下test_func関数がFalseを返すと、raise_exceptionがTrueの場合は403ページに遷移し、False(デフォルト)の場合はログインページに遷移 raise_exception = True def test_func(self): # URLに埋め込まれた主キーから日記データを1件取得 diary = Diary.objects.get(pk=self.kwargs['pk']) # ログインユーザーと日記データの作成ユーザーを比較し、異なればraise_exceptionの設定に従う return self.request.user == diary.user class DiaryDetailView(LoginRequiredMixin, OnlyYouMixin, generic.DetailView): model = Diary template_name = 'diary_detail.html' class DiaryCreateView(LoginRequiredMixin, generic.CreateView): model = Diary template_name = 'diary_create.html' form_class = DiaryCreateForm success_url = reverse_lazy('diary:diary_list') def form_valid(self, form): diary = form.save(commit=False) diary.user = self.request.user diary.save() messages.success(self.request, '日記を作成しました。') return super().form_valid(form) def form_invalid(self, form): messages.error(self.request, "日記の作成に失敗しました。") return super().form_invalid(form) class DiaryUpdateView(LoginRequiredMixin, OnlyYouMixin, generic.UpdateView): model = Diary template_name = 'diary_update.html' form_class = DiaryCreateForm def get_success_url(self): return reverse_lazy('diary:diary_detail', kwargs={'pk': self.kwargs['pk']}) def form_valid(self, form): messages.success(self.request, "日記を更新しました。") return super().form_valid(form) def form_invalid(self, form): messages.error(self.request, "日記の更新に失敗しました。") return super().form_invalid(form) class DiaryDeleteView(LoginRequiredMixin, OnlyYouMixin, generic.DeleteView): model = Diary template_name = 'diary_delete.html' success_url = reverse_lazy('diary:diary_list') def delete(self, request, *args, **kwargs): messages.success(self.request, "日記を削除しました。") return super().delete(request, *args, **kwargs) --------------------------------------------------
attakei

2021/10/25 14:43

Pythonは言語構造としてインデントが非常に重要な言語であり、インデントを無視するコメント欄に記載されても真っ当に読むことが出来ません。 (コード自体のミスなのか、表示上の問題なのか判別不能となるためです) test_views.pyの全文掲載によって文字数に引っかかるのであれば、以下のような対応で質問文に全文掲載できるかを試してください。 - test_views.pyは今回のエラーに引っかかっているテストケースと先頭のimportに絞る - 同じくviews.pyも、今回のテスト対象のクラスと先頭のimportに絞る - この際に、エラーのスタックトレースに連動できるように、エラーで出ている行番号が切り取って表示したどこのコードかをコメントで明記する それが、難しいならGistやPastebinなどといったコード貼り付けサービスを検討してください。
yusuke22

2021/10/25 17:21

ご教示いただきありがとうございます。 先程修正いたしました。
guest

回答1

0

ベストアンサー

※追記依頼に書いた立ち位置としては、「書籍を持っていない」側となります。そのため、挙動上の問題点として回答します。

テストがエラーとなることの整理

アクセス権限コントロールとしてOnlyYouMixinを定義して各Viewクラスに継承させているのですが、test_funcでの権限判定においてDiaryモデルの取得に失敗することを考慮できていません。

python

1class OnlyYouMixin(UserPassesTestMixin): 2 def test_func(self): 3 # ここで存在しない主キーだった場合に、DoesNotExist例外がraiseされる 4 diary = Diary.objects.get(pk=self.kwargs['pk']) 5 # このreturnにたどり着かない 6 return self.request.user == diary.user

テストケースのエラーの通り、ここで登場する例外を適切な処理出来ておらず、サーバーエラーとなっています。(Djangoとしてはサーバーエラーとなるため、テストでも拾うことは出来ない)

テストのクリア例

書籍のことを度外視してテストをパスするだけであれば、以下のような編集で実現できます。

モデルのget時の例外をキャッチする

diff

1+from django.http import Http404 2 3class OnlyYouMixin(UserPassesTestMixin): 4 def test_func(self): 5 # これだと、ここで存在しない主キーだった場合に、ステーテス404のHTTPResponseががraiseされる 6+ try: 7+ diary = Diary.objects.get(pk=self.kwargs['pk']) 8+ except Diary.DoesNotExist: 9+ raise Http404() 10- diary = Diary.objects.get(pk=self.kwargs['pk']) 11 12 return self.request.user == diary.user

便利関数を利用する

Djangoが提供している便利関数にget_or_404というものがあり、これを使うほうが若干楽です。

diff

1+from django.shortcuts import get_object_or_404 2 3 4class OnlyYouMixin(UserPassesTestMixin): 5 def test_func(self): 6 # これだと、ここで存在しない主キーだった場合に、ステーテス404のHTTPResponseががraiseされる 7+ diary = get_object_or_404(Diary, pk=self.kwargs['pk']) 8- diary = Diary.objects.get(pk=self.kwargs['pk']) 9 10 return self.request.user == diary.user

投稿2021/10/26 13:10

編集2021/10/27 10:19
attakei

総合スコア2740

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yusuke22

2021/10/27 09:06

ご回答ありがとうございます。 「便利関数を利用する」にてテスト成功いたしました。 こちらにて書籍での学習を進めさせていただきます。 また、ご報告までに「モデルのget時の例外をキャッチする」でも実施したのですが下記4点のエラーが発生してしまいました。 ====================================================================== ERROR: test_delete_diary_failure (diary.tests.test_views.TestDiaryDeleteView) 日記削除処理が失敗することを検証する ====================================================================== ERROR: test_delete_diary_success (diary.tests.test_views.TestDiaryDeleteView) 日記削除処理が成功することを検証する ====================================================================== ERROR: test_update_diary_failure (diary.tests.test_views.TestDiaryUpdateView) 日記編集処理が失敗することを検証する ====================================================================== ERROR: test_update_diary_success (diary.tests.test_views.TestDiaryUpdateView) 日記編集処理が成功することを検証する ======================================================================
attakei

2021/10/27 10:23

あ、本当ですね。便利関数版を組み立てたときにごっちゃになっていたみたいです。 フィードバックありがとうございました。 (定義もインポートもされない関数を使おうとしてエラーになってました)
yusuke22

2021/10/27 13:03

ご丁寧にありがとうございます。 ベストアンサーとさせていただきます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問