機械学習好きのメモ帳

MMのメモ帳

機械学習、データ分析、アルゴリズムなどを扱っていきます。雑記も含まれます。

Djangoのチュートリアル中にオリジナルのテストを追加した話

はじめに

オリジナルと言っても、
はじめての Django アプリ作成、その 5 | Django ドキュメント | Django
ここにある「さらなるテストについて考える」で述べられているChoicesを持たないQuestionsが公開されないようテストするだけのものですが。
この記事ではコードの一部を記述します。

実装

  • Model
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

このモデルではQuestionとChoiceが一対多の関係となっています。
ちなみにDjangoでは多対一の関係にはForeignKeyを、多対多の関係にはManyToManyFieldを、一対一の関係にはOneToOneFieldを使います。

  • View
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 with any choices.(not including those set to be
        published in the future).
        """
        question = Question.objects.filter(
            pub_date__lte=timezone.now(),
            choice__isnull=False
        )
        return question.order_by('-pub_date')[:5]

このクラス内のget_queryset()で最近の5個のQuestionsを返しています。filterメソッドで現在以前の、Choiceを持っているQuestionという条件で検索し一致するものを返します。

  • Test
def create_question(question_text, days):
    """
    Create a question with the given 'question_text' and published the
    given number of 'days' offset to now(negative for questions published
    in the past, positive for questions that have yet to be published).
    """
    time = timezone.now() + datetime.timedelta(days=days)
    return Question.objects.create(question_text=question_text, pub_date=time)

def create_choice(question, choice_text):
    """
    Create a choice of the given 'question' object.
    """
    question.choice_set.create(choice_text=choice_text, votes=0)

class QuestionIndexViewTests(TestCase):
    ...
    def test_past_question_without_choices(self):
        """
        The questions without choices aren't displayed on the
        index page.
        """
        question = create_question(question_text="Without choices.", days=-30)
        question_with_choice = create_question(question_text="With choice.", days=-30)
        create_choice(question_with_choice, choice_text="Choice 1")
        response = self.client.get(reverse('polls:index'))
        self.assertQuerysetEqual(
            response.context['latest_question_list'],
            ['<Question: With choice.>']
        )

test_past_question_without_choices()でChoiceありとなしのQuestionを作成し、ありのものだけ表示されるようテストしています。

結果

  • Viewにて、choice__isnull=Falseを書かなかった場合
> python manage.py test polls
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
......F....
======================================================================
FAIL: test_past_question_without_choices (polls.tests.QuestionIndexViewTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/*****/Django-Project/mysite/polls/tests.py", line 122, in test_past_question_without_choices
    ['<Question: With choice.>']
  File "/Users/*****/.virtualenvs/django/lib/python3.6/site-packages/django/test/testcases.py", line 955, in assertQuerysetEqual
    return self.assertEqual(list(items), values, msg=msg)
AssertionError: Lists differ: ['<Question: With choice.>', '<Question: Without choices.>'] != ['<Question: With choice.>']

First list contains 1 additional elements.
First extra element 1:
'<Question: Without choices.>'

- ['<Question: With choice.>', '<Question: Without choices.>']
+ ['<Question: With choice.>']

----------------------------------------------------------------------
Ran 11 tests in 0.060s

FAILED (failures=1)
Destroying test database for alias 'default'...
  • 書いた場合
> python manage.py test polls
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
...........
----------------------------------------------------------------------
Ran 11 tests in 0.054s

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

以上のように、テストが通らなかった場合どのテストで、どのように通らなかったのか教えてくれます。