A Complete Beginner's Guide to Djangoのチュートリアルを参考にget_object_or_404ショートカットの利用とリンクのテストをしてみる。
get_object_or_404ショートカット
前回作成したTopic一覧を表示する詳細ページのテストをするためにmyproject/boards/tests.pyにBoardTopicsTestsを追加。
from django.urls import reverse, resolve from django.test import TestCase from .views import home, board_topics from .models import Board class HomeTests(TestCase): # 省略 class BoardTopicsTests(TestCase): def setUp(self): Board.objects.create(name='Django', description='Django board.') def test_board_topics_view_success_status_code(self): url = reverse('board_topics', kwargs={'pk': 1}) response = self.client.get(url) self.assertEquals(response.status_code, 200) def test_board_topics_view_not_found_status_code(self): url = reverse('board_topics', kwargs={'pk': 99}) response = self.client.get(url) self.assertEquals(response.status_code, 404) def test_board_topics_url_resolves_board_topics_view(self): view = resolve('/boards/1/') self.assertEquals(view.func, board_topics)
今回はsetUpメソッドを追加してテストで使用するインスタンスを生成するようにしている。
Djangoのテストは現在のデータベースに対して行うわけではなく、新しいデータベースをその都度生成(マイグレーションおよび破棄)して行っているためテスト用データの準備などはこのsetUpメソッド内で実施するのが良い。
テストの内容はステータスコードを確認するテストと正しいビュー関数が呼ばれているかの確認だが、テストを実行してみるとtest_board_topics_view_not_found_status_codeがDoesNotExist例外で失敗する。
$ python manage.py test Creating test database for alias 'default'... System check identified no issues (0 silenced). .E... ====================================================================== ERROR: test_board_topics_view_not_found_status_code (boards.tests.BoardTopicsTests) ---------------------------------------------------------------------- 省略 boards.models.DoesNotExist: Board matching query does not exist. ---------------------------------------------------------------------- Ran 5 tests in 0.100s FAILED (errors=1) Destroying test database for alias 'default'...
実際にページにアクセスしてみると同じメッセージが表示される。

DEBUG = Falseにしてみると500 Internal Server Errorになっていることがわかる。

今回は404エラーが返ってくるようにしたいのでboards/views.pyのboard_topicsを次のように編集する。Http404のインポートを忘れないように注意。
from django.shortcuts import render from django.http import Http404 from .models import Board def home(request): # 省略 def board_topics(request, pk): try: board = Board.objects.get(pk=pk) except Board.DoesNotExist: raise Http404 return render(request, 'topics.html', {'board': board})
テストを実行してみると全部パスする。
$ python manage.py test Creating test database for alias 'default'... System check identified no issues (0 silenced). ..... ---------------------------------------------------------------------- Ran 5 tests in 0.037s OK Destroying test database for alias 'default'...
ページにアクセスしてみると404ページが表示されることが確認できる。 これはDjangoのデフォルトの404ページだが変更する事もできるようだ。

DEBUG = Falseの場合の表示は下記。

このget() を実行し、オブジェクトが存在しない場合にHttp404を送出することはよく使われるイディオムなので、 Djangoではget_object_or_404()ショートカットが用意されている。
ということでショートカットで書き換えてみる。import文も変更したので注意。
from django.shortcuts import render, get_object_or_404 from .models import Board def home(request): # 省略 def board_topics(request, pk): board = get_object_or_404(Board, pk=pk) return render(request, 'topics.html', {'board': board})
このように1行で簡潔に書くことができる。
一応、python manage.py testを実行して結果が変わっていないことを確認しておく。
ナビゲーションリンクの追加とテスト
メインページから詳細ページへのリンクと詳細ページからメインページへのリンクを追加する。 これについては最初にテストを追加してから機能を実装してみる。
メインページの変更
HomeTestsを以下のように変更する。
class HomeTests(TestCase): def setUp(self): self.board = Board.objects.create(name='Django', description='Django board.') url = reverse('home') self.response = self.client.get(url) def test_home_view_status_code(self): self.assertEquals(self.response.status_code, 200) def test_home_url_resolves_home_view(self): view = resolve('/') self.assertEquals(view.func, home) def test_home_view_contains_link_to_topics_page(self): board_topics_url = reverse('board_topics', kwargs={'pk': self.board.pk}) self.assertContains(self.response, 'href="{0}"'.format(board_topics_url))
setUpメソッドでBoardインスタンスを生成し、homeのレスポンスを再利用できるようにself.responseに設定する。
新しく追加したtest_home_view_contains_link_to_topics_pageテストではassertContainsメソッドを使用してhref="/boards/1/"が含まれているかを確認する。
テストを実行してみると機能はまだ実装していないので当然失敗する。
$ python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
....F.
======================================================================
FAIL: test_home_view_contains_link_to_topics_page (boards.tests.HomeTests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/vagrant/django/myproject/myproject/boards/tests.py", line 21, in test_home_view_contains_link_to_topics_page
self.assertContains(self.response, 'href="{0}"'.format(board_topics_url))
File "/home/vagrant/.local/share/virtualenvs/myproject-j-SR1M6H/lib/python3.6/site-packages/django/test/testcases.py", line 369, in assertContains
self.assertTrue(real_count != 0, msg_prefix + "Couldn't find %s in response" % text_repr)
AssertionError: False is not true : Couldn't find 'href="/boards/1/"' in response
----------------------------------------------------------------------
Ran 6 tests in 0.040s
FAILED (failures=1)
Destroying test database for alias 'default'...
テストがパスするようにtemplates/home.htmlを編集(<tbody>内のみ表示)。
<tbody> {% for board in boards %} <tr> <td> <a href="{% url 'board_topics' board.pk %}">{{ board.name }}</a> <small class="text-muted d-block">{{ board.description }}</small> </td> <td class="align-middle">0</td> <td class="align-middle">0</td> <td></td> </tr> {% endfor %} </tbody>
テンプレートでURLを記述する場合は、URLの変更に対応できる{% url %}タグを使用するのが良い。
{% url %}タグの最初の引数(board_topics)はURLconfのnameで、残りは<int:pk>などに対する引数を指定する。
リンクを追加したのでこれでテストが通るようになる。
$ python manage.py test Creating test database for alias 'default'... System check identified no issues (0 silenced). ...... ---------------------------------------------------------------------- Ran 6 tests in 0.055s OK Destroying test database for alias 'default'...
メインページにアクセスしてみるとリンクが追加されている。

詳細ページの変更
次に詳細ページからメインメージへのリンク用のテストを追加する。
class BoardTopicsTests(TestCase): # 省略 def test_board_topics_view_contains_link_back_to_homepage(self): board_topics_url = reverse('board_topics', kwargs={'pk': 1}) response = self.client.get(board_topics_url) homepage_url = reverse('home') self.assertContains(response, 'href="{0}"'.format(homepage_url))
実装していないので同じくテストは失敗する。
$ python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.F.....
======================================================================
FAIL: test_board_topics_view_contains_link_back_to_homepage (boards.tests.BoardTopicsTests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/vagrant/django/myproject/myproject/boards/tests.py", line 45, in test_board_topics_view_contains_link_back_to_homepage
self.assertContains(response, 'href="{0}"'.format(homepage_url))
File "/home/vagrant/.local/share/virtualenvs/myproject-j-SR1M6H/lib/python3.6/site-packages/django/test/testcases.py", line 369, in assertContains
self.assertTrue(real_count != 0, msg_prefix + "Couldn't find %s in response" % text_repr)
AssertionError: False is not true : Couldn't find 'href="/"' in response
----------------------------------------------------------------------
Ran 7 tests in 0.059s
FAILED (failures=1)
Destroying test database for alias 'default'...
templates/topics.htmlを編集してリンクを追加。
{% load static %}<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{{ board.name }}</title>
<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
</head>
<body>
<div class="container">
<ol class="breadcrumb my-4">
<li class="breadcrumb-item"><a href="{% url 'home' %}">Boards</a></li>
<li class="breadcrumb-item active">{{ board.name }}</li>
</ol>
</div>
</body>
</html>
これでテストが通るようになる。
$ python manage.py test Creating test database for alias 'default'... System check identified no issues (0 silenced). ....... ---------------------------------------------------------------------- Ran 7 tests in 0.047s OK Destroying test database for alias 'default'...
詳細ページにもリンクが付いたのでメインページとの行き来ができるようになった。

まとめ
- テストデータの準備などは
setUpメソッドで実施 get_object_or_404()ショートカットが提供されているassertContainsはレスポンスのコンテンツ内に指定文字列が入っているかどうか確認- テンプレートでのURLの記述には
{% url %}を使用