코드에서 테스트를 사용하는 이유
미래에 생성 날짜를 가진 question에 대해서 was_published_recently()
함수가 제대로 작동하는지 확인한다.
- import datetime
- from django.utils import timezone
- from polls.models import Question
- future_question = Question(pub_date=timezone.now() + datetime.timedelta(days=30))
- future_question.was_published_recently()
위 명령어로 현재 날짜 + 30일인 날짜를 datetime으로 가지는 미래 질문을 만든다.
True를 return하는 것을 확인할 수 있다.
하지만 이렇게 수동으로 테스트하는 것은 자동화된 테스트로 수행할 수 있으므로 자동화된 테스트로 바꾸도록 하자.
테스트 코드의 이름은 test
로 시작되어야 한다. 테스트 코드 함수의 prefix또한 test
로 시작해야 한다.
polls/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) # 테스트의 결과값이 False가 나오길 기대한다.
- python manage.py test polls
위 명령어를 사용하여 테스트를 실행해 보자.
False를 기대했지만 True가 나와 테스트를 통과하지 못한 것을 확인할 수 있다.
그러면 was_published_recently()
함수를 수정하여 버그를 수정해 보겠다.
polls/models.py
def was_published_recently(self):
now = timezone.now()
return now - datetime.timedelta(days=1) <= self.pub_date <= now
테스트가 정상적으로 성공한 것을 확인할 수 있다.
하나의 버그를 고치면서 다른 새로운 버그가 생기면 곤란할 것이다. 보다 포괄적인 테스트를 위해 동일한 클래스에 두 가지 테스트 메소드를 추가하자.
polls/tests/py
def test_was_published_recently_with_old_question(self):
"""
was_published_recently() returns False for questions whose pub_date
is older than 1 day.
"""
time = timezone.now() - datetime.timedelta(days=1, seconds=1)
old_question = Question(pub_date=time)
self.assertIs(old_question.was_published_recently(), False)
def test_was_published_recently_with_recent_question(self):
"""
was_published_recently() returns True for questions whose pub_date
is within the last day.
"""
time = timezone.now() - datetime.timedelta(hours=23, minutes=59, seconds=59)
recent_question = Question(pub_date=time)
self.assertIs(recent_question.was_published_recently(), True)
기능을 명확하게 하기 위해 테스트 코드는 충분할수록 좋다.
함수의 기능에 대한 테스트를 했다면 view에대한 테스트를 진행한다. 실제 client가 하는것 처럼 request를 보내고 response를 받아서 사용자의 요청을 잘 수행하는지 확인한다. 먼저 shell을 실행한다.
- python manage.py shell
- from django.test.utils import setup_test_environment
- setup_test_environment()
- from django.test import Client
- client = Client.get('/') # get은 조회를 위한 호출 방식 post는 data생성 또는 수정을 위한 호출 방식 (400)
- response.status_code
- from django.urls import reverse
- response = client.get(reverse("polls:index"))
- response.status_code #200
- response.content
- response.context["latest_question_list"]
지난 포스트에서 ListView 클래스 기반 뷰를 소개했었는데 get_queryset()
메소드를 수정하여 날짜를 검사하도록 한다.
polls/view.py
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")[ # less than equal
:5
]
test.py에 함수를 추가한다.
polls/test.py
from django.urls import reverse
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)
class QuestionIndexViewTests(TestCase):
def test_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"], [])
def test_past_question(self):
"""
Questions with a pub_date in the past are displayed on the
index page.
"""
question = create_question(question_text="Past question.", days=-30)
response = self.client.get(reverse("polls:index"))
self.assertQuerySetEqual(
response.context["latest_question_list"],
[question],
)
def test_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"], [])
def test_future_question_and_past_question(self):
"""
Even if both past and future questions exist, only past questions
are displayed.
"""
question = 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],
)
def test_two_past_questions(self):
"""
The questions index page may display multiple questions.
"""
question1 = create_question(question_text="Past question 1.", days=-30)
question2 = create_question(question_text="Past question 2.", days=-5)
response = self.client.get(reverse("polls:index"))
self.assertQuerySetEqual(
response.context["latest_question_list"],
[question2, question1],
)
테스트를 위해 create_question함수를 만들었다. 각각의 상황에 대해서 함수를 만들어 테스트를 하였다.
- 데이터가 없는 경우
- 과거 데이터인 경우
- 미래 데이터인 경우
- 과거데이터 1개 & 미래 데이터 1개
- 과거데이터 2개
성공적으로 테스트를 통과한다.
모든 테스트를 통과하였다. 그러나 목록에는 나타나지 않는 미래의 질문에 대해서 사용자가 URL을 알고있거나 추측하게 되면 접근할 수 있다. 제약조건을 걸어 이를 수정하자.
polls/view.py
class DetailView(generic.DetailView):
...
def get_queryset(self):
"""
Excludes any questions that aren't published yet.
"""
return Question.objects.filter(pub_date__lte=timezone.now())
이전 테스트 케이스인 test_future_question
과 test_future_question_and_past_question
에 대해서 실패를 하는 모습을 보여주고 있고, 새로운 테스트 케이스에 대해서는 성공을 하였다.