DreamerDreamのブログ

夢想家の夢です。〜揚げたてのモヤっとしたものをラフレシアと共に〜

Django奮闘記⑧憂鬱ーテストー

前回は謎が解決できてヤレヤレでした。

dreamerdream.hateblo.jp

 

 

今回もこちらの続きをします

はじめての Django アプリ作成、その 4 | Django documentation | Django

 

今まで作ったプロジェクトを「汎用view」というのに対応させるのですね。

実は汎用viewを使う場合が殆どなんだけど、中身を理解するためにわざわざ遠回りさせたとか。

まあ、あまり理解出来てないんですけどね(汗

raspberrypi:~/draemon $ nano polls/urls.py

まずはursを弄って

from django.urls import path

from . import views


app_name = 'polls'
urlpatterns = [
# ex: /polls/
path('', views.index, name='index'),
# ex: /polls/5/
path('<int:pk>/', views.detail, name='detail'),
# ex: /polls/5/results/
path('<int:pk>/results/', views.results, name='results'),
# ex: /polls/5/vote/
path('<int:question_id>/vote/', views.vote, name='vote'),
]

 

変更はpkだけ?

 

raspberrypi:~/draemon $ nano polls/views.py

viewsのvote以外を入れ替えます。

from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.views import generic

from .models import Choice, Question


class IndexView(generic.ListView):
  template_name = 'polls/index.html'
  context_object_name = 'latest_question_list'

  def get_queryset(self):
    """Return the last five published questions."""
    return Question.objects.order_by('-pub_date')[:5]


class DetailView(generic.DetailView):
  model = Question
  template_name = 'polls/detail.html'


class ResultsView(generic.DetailView):
  model = Question
  template_name = 'polls/results.html'

 

def vote(request, question_id):
  question = get_object_or_404(Question, pk=question_id)

  try:
    selected_choice = question.choice_set.get(pk=request.POST['choice'])
  except (KeyError, Choice.DoesNotExist):
    # Redisplay the question voting form.
    return render(request, 'polls/detail.html', {
      'question': question,
      'error_message': "You didn't select a choice.",
      })
  else:
    selected_choice.votes += 1
    selected_choice.save()
    # Always return an HttpResponseRedirect after successfully dealing 
    # with POST data. This prevents data from being posted twice if a
    # user hits the Back button.
    return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))

実行してみると、あら?エラー?焦りません。

File "/home/---/draemon/polls/urls.py", line 9, in <module>
path('', views.index, name='index'),
AttributeError: module 'polls.views' has no attribute 'index'

indexが無いと言われる?

ん〜?

よく見るとpoll/urls.pyの変更がpkだけだと勘違いしてたのが原因でした。しっかりクラス名に変わっとる!全部入れ替えます!

urlpatterns = [
path('', views.IndexView.as_view(), name='index'),
path('<int:pk>/', views.DetailView.as_view(), name='detail'),
path('<int:pk>/results/', views.ResultsView.as_view(), name='results'),
path('<int:question_id>/vote/', views.vote, name='vote'),
]

実行すると、ちゃんと出ました。これで入れ替え作業が完了しました。

f:id:DreamerDream:20180905103249p:plain

 

 

 

チュートリアル5/7ページ目です。先が見えてきました。頑張ります!

はじめての Django アプリ作成、その 5 | Django documentation | Django

「自動テスト」という何とも便利そうな機能があるようです。

アプリケーション毎にテストを書くようにアプリディレクトリ内のtests.pyをサンプル通りに書きます。

import datetime

from django.test import TestCase
from django.utils import timezone

from .models import Question


class QuestionModelTests(TestCase):

  def test_was_published_recently_with_future_question(self):
    """
    was_published_recently() returns False for questions whose pub_date
    is in the future.
    """
    time = timezone.now() + datetime.timedelta(days=30)
    future_question = Question(pub_date=time)
    self.assertIs(future_question.was_published_recently(), False)

 

 

実行するにはmanage.pyで実行するそうです。

実行結果

raspberrypi:~/draemon $ python3 manage.py test polls
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
F
======================================================================
FAIL: test_was_published_recently_with_future_question (polls.tests.QuestionModelTests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/---/draemon/polls/tests.py", line 18, in test_was_published_recently_with_future_question
self.assertIs(future_question.was_published_recently(), False)
AssertionError: True is not False

----------------------------------------------------------------------
Ran 1 test in 0.023s

FAILED (failures=1)
Destroying test database for alias 'default'...

 

チュートリアル通りの結果です。

お手本と居りにmodels.pyを修正します。

raspberrypi:~/draemon $ nano polls/models.py

未来の日付を指定できないように。現在と指定日時の計算をします。

import datetime

from django.db import models
from django.utils import timezone

# Create your models here.
from django.db import models


class Question(models.Model):
  question_text = models.CharField(max_length=200)
  pub_date = models.DateTimeField('date published')
  def __str__(self):
    return self.question_text
  def was_published_recently(self):
    now = timezone.now()
    return now - datetime.timedelta(days=1) <= self.pub_date <= now

class Choice(models.Model):
  question = models.ForeignKey(Question, on_delete=models.CASCADE)
  choice_text = models.CharField(max_length=200)
  votes = models.IntegerField(default=0)
  def __str__(self):
    return self.choice_text

 

で、修正後にもう一度テスト

結果

raspberrypi:~/draemon $ python3 manage.py test polls
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.
----------------------------------------------------------------------
Ran 1 test in 0.018s

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

これもチュートリアル通りの結果です。よしよし

けど、素人目にはガッツリテストコード書いてるので全然自動感がないし、正直する意味がよくわからない。

けど、チュートリアル読み飛ばして痛い目に遭ったから今回は読み飛ばさない!

 

対話shellで何やらテストが出来るようです。

raspberrypi:~/draemon $ python3 manage.py shell
Python 3.5.3 (default, Jan 19 2017, 14:11:04)
[GCC 6.3.0 20170124] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from django.test.utils import setup_test_environment
>>> setup_test_environment()
>>> from django.test import Client
>>> client = Client()
>>> response = client.get('/')
Not Found: /
>>> response.status_code
404
>>> from django.urls import reverse
>>> response = client.get(reverse('polls:index'))
>>> response.status_code
200
>>> response.content
b'\n <ul>\n \n <li><a href="/polls/2/">What&#39;s up?</a></li>\n \n <li><a href="/polls/1/">What&#39;s UP?</a></li>\n \n </ul>\n\n'
>>> response.context['latest_question_list']
<QuerySet [<Question: What's up?>, <Question: What's UP?>]>
>>> exit()
うん、Questionの値が一覧出来るのですね?

はい、それ以外はサッパリ解りません!まあいいのです。

ちゃんと例題通りに動いてるしこういうテストができるんだよーっていう紹介だと見ておきます。

 

次に未来の質問を見せないように改造するのですね。

polls/views.py

を改造してtimezoneをimport

get_queryset(self)移行を変更。

Question.objects.filterを通すと未来が見えなくなるのですね!便利なフィルターだ。

from django.utils import timezone
class IndexView(generic.ListView):
  template_name = 'polls/index.html'
  context_object_name = 'latest_question_list'

  def get_queryset(self):
    """
    Return the last five published questions (not including those set to be
    published in the future).
    """
    return Question.objects.filter(
         pub_date__lte=timezone.now()
    ).order_by('-pub_date')[:5]

 その後テスト用プログラムを書いて〜(書いたけどなんか長いので省略)

 

Detail.viewにも同じ制約を付けるんですね。

polls/views.py

DetailViewに追加

class DetailView(generic.DetailView):
  model = Question
  template_name = 'polls/detail.html'

  def get_queryset(self):
    """
    Excludes any questions that aren't published yet.
    """
    return Question.objects.filter(pub_date__lte=timezone.now())

 んでまたテストを書く。(よくわかんない)

結果

raspberrypi:~/draemon $ python3 manage.py test polls
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.......
----------------------------------------------------------------------
Ran 7 tests in 1.406s

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

 テスト合格っぽい。けどよくわかんない。

チュートリアルによると

・テストは長くても多くても構わない

・テストコードは出来る限り書くべき

ということだそうです。わかる。わかるけど面倒だしやっぱり解んない。

まあ大規模なwebサイトだったり開発が独りで無い場合ほど重要そうだってのは解った気がする。

今日はこのぐらいで。

 

dreamerdream.hateblo.jp