defcreate_question(question_text, days): """ Create a question with the given 'question_text' and published the given number of 'days' offset to now (negative for question 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)
classQuestionIndexViewTest(TestCase): deftest_no_questions(self): """ If no questions exist, an appropriate message is displayed. """ response = self.client.get(reverse('polls:index')) self.assertEqual(response.status_code, 200) self.assertContains(response, "No polls are available") self.assertQuerysetEqual(response.context['latest_question_list'], []) deftest_past_question(self): """ Questions with a pub_date in the past are displayed on the index page """ create_question(question_text="Past question.", days=30) response = self.client.get(reverse('polls:index')) self.assertQuerysetEqual( response.context['latest_question_list'], ['<Question: Past quesiton.>'] ) deftest_future_question(self): """ Questions with a pub_date in the future aren't displayed on the index page. """ create_question(question_text="Future question.", days=30) response = self.client.get(reverse('polls:index')) self.assertContains(response, "No polls are available.") self.assertQuerysetEqual(response.context['latest_question_list'], [])
deftest_future_question_and_past_question(self): """ Even if both past and future questions exist, only past questions are displayed. """ create_question(question_text="Past question.", days=-30) create_question(question_text="Future question.", days=30) response = self.client.get(reverse('polls:index')) self.assertQuerysetEqual( response.context['latest_question_list'], ['<Question: Past question.>'] )
deftest_two_past_questions(self): """ The questions index page may display multiple questions. """ create_question(question_text="Past question 1.", days=-30) create_question(question_text="Past question 2.", days=-5) response = self.client.get(reverse('polls:index')) self.assertQuerysetEqual( response.context['latest_question_list'], ['<Question: Past question 2.>', '<Question: Past question 1.>'] )
세부사항들을 잠시 체크하고 넘어갑니다.
첫번째로, quesiton 숏컷 함수인, create_question 은 질문 생성하는데에 반복되는 부분을 책임집니다.
test_no_questions 는 질문을 생성하지 않습니다. 하지만 “No polls are available” 이라는 메시지를 체크 하고, latest_question_list 가 비어있는것을 검증합니다. django.test.TestCase 클래스는 추가적인 assertion 메써드들을 제공합니다.
해당 예시에서는, assertContains() 그리고 assertQuerysetEqual() 을 사용합니다.
test_past_question 에서는, 질문을 생성하고 리스트에 표시되는지 확인 합니다.
test_future_question 에서는, pub_date 가 미래일인 질문을 생성합니다. 데이터베이스는 각 테스트 메써드 마다 재설정 됩니다.
따라서, 첫번째 질문은 더이상 데이터베이스에 존재하지 않습니다. 그러므로, 인덱스는 아무 질문들도 가지고 있지 않아야 합니다.
추가적으로, 우리는 관리자 입력값과 사용자 경험 그리고 각 상태 와 각 변경사항, 예상된 결과들의 발행 같은 것들을 테스트를 통해 하나의 이야기를 만들어 가는겁니다.
python manage.py test polls 를 실행하면, 8개의 테스트가 진행되고 OK 가 됩니다.
1 2 3 4 5 6 7 8
Creating test database foralias'default'... System check identified no issues (0 silenced). ........ ---------------------------------------------------------------------- Ran 8 tests in 0.031s
OK Destroying test database foralias'default'...
DetailView 테스트 하기
우리가 또 해결해야 하는것은, 미래의 질문들이 인덱스에 표시가 되지 않는다 하더라도,
사용자들이 정확한 URL 을 알고 있거나, 추측할수 있다면, 사용자들은, 여전히 질문들에 접근할수 있습니다.
따라서, DetailView 에 비슷한 제약을 추가해 줍니다.
1 2 3 4 5 6 7
classDetailView(generic.DetailView): ... defget_queryset(self): """ 아직 발행되지 않은 질문들을 제외합니다. """ return Question.objects.filter(pub_date__lte=timezone.now())
그리고, 물론, 몇개의 테스트를 추가해 줄겁니다. pub_date 가 미래는 출력이 되지 않는 상황에서,
classQuestionDetailViewTests(TestCase): deftest_future_question(self): """ The detail view of a question with a pub_date in the future returns a 404 not found. """ future_question = create_question(question_text='Future question.', days=5) url = reverse('polls:detail', args=(future_question.id,)) response = self.client.get(url) self.assertEqual(response.status_code, 404)
deftest_past_question(self): """ The detail view of a question with a pub_date in the past displays the question's text. """ past_question = create_question(question_text='Past Question.', days=-5) url = reverse('polls:detail', args=(past_question.id,)) response = self.client.get(url) self.assertContains(response, past_question.question_text)
python manage.py test polls 를 실행하면,
아래와 같이 10가지의 테스트 케이스가 OK 됩니다.
1 2 3 4 5 6 7 8
Creating test database foralias'default'... System check identified no issues (0 silenced). .......... ---------------------------------------------------------------------- Ran 10 tests in 0.047s
OK Destroying test database foralias'default'...
더많은 테스트를 위한 아이디어
비슷한 개념의 get_queryset 메써드를 ResultsView 에 추가해 주어야 하고 새로운 테스트 클래스를 생성 해주어야 합니다.. 방금 DetailView 를 위해 한 작업과 굉장히 비슷한 작업이 될것이고, 사실 반복적인 작업이 될것 입니다.
또한 다른 방식으로도 어플리케이션을 향상 시킬수 있습니다. 예를 들어, Choices 가 없는 Questions 를 사이트에 발행 시키는것은 어리석은 짓입니다. 따라서, 우리의 뷰들은 이것에 대해 체크하고 Choice 가 없는 Questions 들을 제외할수 있습니다.
우리의 테스트들은 Choices 가 없는 Question 을 생성하고, 발행이 되지 않았는지 테스트 하고 비슷한 Question 인데 Choices 가 있는 것을 생성해서, 발행 되는지 테스트 합니다.
아마도, 로그인이 되어있는 관리자들은 발행이 취소된 Questions 도 볼수 있어야 합니다. 하지만, 일반 사이트 방문자는 볼수 없어야 하겠죠. 다시한번, 이것을 위해서 소프트웨어에 추가되어야 하는 그 어떤것이던지,
테스트와 함께 병행 되어야 합니다. 테스트를 먼저 작성하고 테스트를 통과하는 코드를 작성하던,
로직을 먼저 생각한다음에 테스트를 작성하여 증명하던지.
어떤 방식으로던, 테스트와 같이 병행되어야 합니다.
마치며..
view 들을 테스트 해보았는데.
아직은 test 를 작성할 상황이 많지 않았어서 그런지.. 전반적인 순서라던지, 감이 오지 않네요.
작동은 완벽하게 되지만, 코드 동작 자체의 연계성에 대한 이해가 좀 부족한것 같습니다;;;