안녕하세요. 상당히 오랜만에 돌아온 개발자 정씨입니다.
그럼 오늘도 여김없이 바로 시작해보도록 하죠
1. 테스트
오류검증은 어느 프로그래밍에서든 중요한 부분이라고 생각합니다.
Django에서는 어떤방식으로 오류를 검증할 수 있는지 확인해보도록 하겠습니다.
# polls/models.py
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):
return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
polls/model.py를 보면 was_published_recently()라는 함수가 존재합니다.
해당 함수는 어제, 오늘 게시된 Question을 확인하는 것을 목적으로 작성된 함수임을 알 수 있습니다.
하지만, 어제 오늘이 아닌 미래의 날짜에 발행되었을 경우에도 True를 반환하게 됩니다. 이는 목적과 부합하지 않습니다.
> python manage.py shell
> import datetime
> from django.utils import timezone
> from polls.models import Question
> # create a Question instance with pub_date 30 days in the future
> future_question = Question(pub_date=timezone.now() + datetime.timedelta(days=30))
> # was it published recently?
> future_question.was_published_recently()
True
실제로 shell을 이용하여 미래에 Question을 발행하였을때, was_published_recently()는 True를 반환합니다.
# 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)
위의 shell 내용을 코드 내재화 한다면, 위와 같이 작성이 가능합니다.
> python manage.py test polls
위와 같은 명령어를 통해 테스트를 진행할 수 있습니다.
아마 결과 내용으로는 Error가 반환 될 것 입니다.
# polls/models.py
def was_published_recently(self):
now = timezone.now()
return now - datetime.timedelta(days=1) <= self.pub_date <= now
위와 같이 어제와 오늘에만 True값을 반환하도록 was_published_recently() 수정하고,
테스트를 진행하게 되면 이번에는 OK가 반환되는 것을 확인 할 수 있습니다.
# 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)
polls/tests.py를 위와같이 수정하여 보다 정밀한 테스트 또한 가능합니다.
2. 테스트 클라이언트
Django에서는 tests.py를 작성하지 않아도 shell을 통해 테스트 할 수 있는 클라이언트를 제공합니다.
> python manage.py shell
> from django.test.utils import setup_test_environment
> setup_test_environment()
> from django.test import Client
> # create an instance of the client for our use
> client = Client()
위와 입력하여 테스트 클라이언트를 실행합니다.
> # get a response from '/'
> response = client.get('/')
Not Found: /
> # we should expect a 404 from that address; if you instead see an
> # "Invalid HTTP_HOST header" error and a 400 response, you probably
> # omitted the setup_test_environment() call described earlier.
> response.status_code
404
> # on the other hand we should expect to find something at '/polls/'
> # we'll use 'reverse()' rather than a hardcoded URL
> 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/1/">What's up?</a></li>\n \n </ul>\n\n'
> response.context['latest_question_list']
<QuerySet [<Question: What's up?>]>
위와 같은 과정들을 통해 테스트를 진행할 수 있습니다.
3. 뷰(View) 테스트
이번에는 View에서 미래에 발행된 질문들이 보이지 않게끔 수정해보고 이를 테스트 해보도록 합시다.
# polls/views.py
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]
polls/views.py를 위와 같이 수정하여 오늘날짜 이전의 Question들만 리스팅 할 수 있도록 수정합니다.
# polls/tests.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],
transform=lambda x: x
)
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],
transform=lambda x: x
)
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],
transform=lambda x: x
)
* Document와 다르게 "transform=lambda x: x" 코드가 들어간 함수가 있습니다
* "AssertionError: Lists differ: ['<Question: Past question.>'] != [<Question: Past question.>]" 오류가 나는경우 참조하세요 (작은따옴표 차이)
그리고 위와 같이 polls/tests.py를 수정하여 다양한 Case를 테스트 해볼 수 있도록 합니다.
해당 함수들은 각각 아래와 같은 경우를 Test하고 있음을 확인 할 수 있습니다.
- test_no_questions(): Question이 존재하지 않는 경우
- test_past_question(): Question이 과거(30일 전)에 발행된 경우
- test_future_question(): Question이 미래(30일 후)에 발행된 경우
- test_future_question_and_past_question(): Question이 미래(30일 후)에 하나, 과거(30일 전)에 하나 발행된 경우
- test_two_past_questions(): Question이 과거(각각 30일 전, 5일 전)에 2개 발행된 경우
Test 기능을 사용하면 보다 면밀하게 프로그래밍을 할 수 있을 것 같다는 생각이듭니다. 참 좋은 기능이네요
조만간 또 돌아오겠습니다. 항상 감사드립니다!
'Django 맛보기' 카테고리의 다른 글
Django 맛보기6 - 심화된 뷰(View) 만들기 (0) | 2021.09.17 |
---|---|
Django 맛보기5 - 뷰 만들기 (0) | 2021.09.07 |
Django 맛보기4 - 관리자 생성하기 (0) | 2021.09.06 |
Django맛보기3 - DB설치 및 모델 생성 (0) | 2021.09.06 |
Django 맛보기2 - 설치, 프로젝트생성, Github 업로드 (0) | 2021.09.03 |