์์ฑํ ์ฝ๋๋ค์ ๋ํด ํ ์คํธ๋ฅผ ํ ์ ์๋ ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํด๋ณด๋ ค๊ณ ํ๋ค.
ํ๋์ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ ์ฝ๋๋ฅผ ์์ฑํ๋ฉด,
๊ฒฐ๊ณผ๊ฐ ์ํ๋ ๊ฐ์ผ๋ก ๋์ค๋์ง์ ๋ํด ํ
์คํธ ์ฝ๋๋ฅผ ์์ฑํ๋ ๊ฒ์ด๋ค.
ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํด์ผ๋๋ ์ด์ ๋ ๋ค์๊ณผ ๊ฐ๋ค.
๊ธฐ๋ฅ๊ฐ๋ฐ์ ๋ํ ์ฝ๋๋ฅผ ํ
์คํธ ์ฝ๋๋ฅผ ๋จผ์ ์์ฑํ๊ณ ๋๋ค, ๊ธฐ๋ฅ๊ฐ๋ฐ์ ์์ฑํ๋ ๊ฒ์ ํ
์คํธ ์ฃผ๋ ๊ฐ๋ฐ(TDD
)์ด๋ผ ํ๋ค.
์ง๊ธ๊น์ง ์์ฑํ ์ฝ๋์๋ ๋ฒ๊ทธ๊ฐ ์๋๋ฐ,
๋ชจ๋ธ ์ฝ๋ ์ค ๋ฐ์ดํฐ๊ฐ ์ต๊ทผ์ด๋ ์๋๋ ๋ฐํํ๋ ํจ์(was_published_recently(self))
์์ ๋ฌธ์ ๊ฐ ํ๋ ์๋ค.
์์ฑ๋ ์ง๊ฐ ๋ฏธ๋์ ์์ฑ๋ ๋ฐ์ดํฐ๋ ์ต๊ทผ์ผ๋ก ๊ฐ์ฃผํ์ง ์๋๋ค. ๋ฏธ๋์ ๋ ์ง๋ ๊ฑฐ์ง์ผ๋ก ๋์์ผํ๋ ๊ฒ์ด๋ค.
ํ์ง๋ง ์ง๊ธ ์ฝ๋๋ฅผ ๋ณด๋ฉด ๋ฏธ๋์ ๋ํ ์ฝ๋์ฒ๋ฆฌ๊ฐ ์๋ค.
๋ฐ๋ผ์ shell์์ ๊ฒฐ๊ณผ๊ฐ์ด ์ฐธ์ธ์ง ๊ฑฐ์ง์ธ์ง ํ
์คํธ๋ฅผ ํด๋ณด๋ ค๊ณ ํ๋ค.
py manage.py shell
>>> 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()
True
ํ
์คํธ๋ฅผ ์ํด ๊ด๋ จ ํจํค์ง๋ค์ importํ๊ณ
ํ์ฌ๋ ์ง์ 30์ผ์ ๋ํ ๋ฏธ๋์ ์์ฑ๋ ์ง๋ฅผ ๊ฐ์ง ์ง๋ฌธ(question)์ ํ๋ ๋ง๋ค์ด์ค๋ค.
๋ค์ ์ด๊ฒ์ด ์ต๊ทผ ์์ฑ๋ ์ง์ธ์ง ๋ฌป๋ ์๋ณํจ์๋ฅผ ํธ์ถํ์์ ๋, ๊ฒฐ๊ณผ ๊ฐ์ด True
๊ฐ ๋์ค๋ ๊ฒ์ ํ์ธ ํ ์ ์๋ค.
๋ฏธ๋๋ ์ต๊ทผ์ด ์๋๊ธฐ ๋๋ฌธ์ ์ด๊ฒ์ ์๋ชป๋ ๊ฒ์ด๋ค.
์ด๋ฌํ ํ ์คํธ๋ฅผ shell์์ ์๋์ ์ผ๋ก ํ๋ ๊ฒ์ด ์๋ ๋ช ๋ น์ด ํ๋๋ก ํ ์คํธ๋ฅผ ํด๋ณผ ์ ์๋ ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํด๋ณผ ๊ฒ์ด๋ค.
ํ
์คํธ ์ฝ๋๋ ์ผ๋ฐ์ ์ผ๋ก ์ฑ ๋ด๋ถ์ polls/tests.py
์ ์์ฑํ๋ค.
์ฝ๋ ์์ฑ์ ํจ์ ์ด๋ฆ์ ์๊ธ์๋ test
๋ก ์์ํด์ฃผ๋ฉด,
๋ช
๋ น์ด ์คํ์ ์์ฑํ 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)
์ฝ๋๋ฅผ ์ดํด๋ณด๋ฉด,
์ shell์์ ํด์ค ๊ฒ๊ณผ ๋น์ทํ๋ค. 30์ผ ๋ฏธ๋์ ์ง๋ฌธ์ ์์ฑํ๋ค, ์ด ์ง๋ฌธ์ ๋ํด ์์ฑ์ผ์ด ์ต๊ทผ์ด๋๊ณ ๋ฌผ์์๋ ๊ฒฐ๊ณผ๊ฐ์ด False
๊ฐ ๋์ค๋ ๊ฒ์ ํ์ธํ๋ ๊ฒ์ด๋ค.
๊ทธ๋ผ ๋ช ๋ น์ด๋ฅผ ์คํํ์ฌ ํ ์คํธ ์ฝ๋์ ๊ฒฐ๊ณผ๋ฅผ ํ์ธํด ๋ณด๊ฒ ๋ค.
ํฐ๋ฏธ๋์์ ํ ์คํธ๋ฅผ ์คํํ๋ค.
py 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 "/path/to/mysite/polls/tests.py", line 16, 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.001s
FAILED (failures=1)
Destroying test database for alias 'default'...
ํ
์คํธ ์์์ ๊ฒฐ๊ณผ๊ฐ์ด False
๊ฐ ๋์์ผ ํ๋๋ฐ True
๊ฐ ๋์ ํ
์คํธ์ ์คํจํ๋ค๋ ๊ฒ์ด๋ค.
๊ทธ๋ผ ์ด์ ํ ์คํธ๊ฐ ํต๊ณผ๋ ์ ์๋๋ก ์ฝ๋๋ฅผ ์์ ์ ํด์ค์ผ ํ๋ค.
- polls/models.py
def was_published_recently(self):
now = timezone.now()
return now - datetime.timedelta(days=1) <= self.pub_date <= now
question
์ ์์ฑ๋ ์ง๊ฐ ๋ฏธ๋๋ก ๋์ด๊ฐ์ง ์๋๋ก
์ต๊ทผ์ ๊ธฐ์ค์ ํ์ฌ๋ ์ง๋ฅผ ๋๊ณ ํ๋ฃจ๋ก ์ก์๋์๋ค.
์์ ์ ํด์ค๋ค ๋ค์ ํ ์คํธ๋ฅผ ์งํํด๋ณด๊ฒ ๋ค.
py manage.py test polls
๊ทธ๋ผ ๋ค์๊ณผ ๊ฐ์ด ์์ ํด์ค ์ฝ๋๊ฐ ํ ์คํธ๋ฅผ ์ฑ๊ณต์ ์ผ๋ก ํต๊ณผํ์์ ํ์ธ ํ ์ ์๋ค
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK
Destroying test database for alias 'default'...
ํ ์คํธ ์ฝ๋๋ฅผ ํตํด ๊ธฐ๋ฅ์ ๋์ฑ ๋ช ํํ ํ๊ธฐ ์ํด ๋ณด๋ค ํฌ๊ด์ ์ธ ํ ์คํธ ์ฝ๋๋ฅผ ์ถ๊ฐํด์ค ์ ์๋ค.
๋ค์๊ณผ ๊ฐ์ด 1์ผ์ด ๋์ด๊ฐ๋์ ๋ํ ๊ฒฐ๊ณผ๊ฐ์ด False
๊ฐ ๋์ค๊ณ
1์ผ์ด ๋์ด๊ฐ์ง ์์์๋์ ๋ํ ๊ฒฐ๊ณผ๊ฐ์ด True
๊ฐ ๋์ค๋๋ก ๊ฒ์ฆ์ ํ๋ ํ
์คํธ ์ฝ๋๋ฅผ ์์ฑํด์ฃผ๋ ๊ฒ์ด๋ค.
-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)
์ด๋ ๊ฒ ๋ฐ์ํ ์ ์๋ ๊ฒฝ์ฐ์ ๋ํ์ฌ ์ต๋ํ ํ ์คํธ ์ฝ๋๋ฅผ ์ถฉ๋ถํ๊ฒ ์์ฑํด์ฃผ๋ ๊ฒ์ด ์ข๋ค
๋ทฐ ํ
์คํธ๋ ์ฌ์ฉ์๊ฐ ๋๊ฒ์ฒ๋ผ request
(์์ฒญ)์ ํ๊ณ response
๋ฅผ ๋ฐ์ ์ํ๋ ๊ฒฐ๊ณผ๊ฐ์ ๋ฐ์๋์ง ํ์ธํ๋ ๊ฒ์ด๋ค.
์ฅ๊ณ ๋ ์ฌ์ฉ์๋ฅผ ์๋ฎฌ๋ ์ดํธํ๊ธฐ ์ํด ํ
์คํธ ํด๋ผ์ด์ธํธ ํด๋์ค์ธ Client
๋ฅผ ์ ๊ณตํ๋ค.
์ด ํ
์คํธ ํด๋ผ์ด์ธํธ๋ tests.py
๋๋ shell
์์ ์ฌ์ฉํ ์ ์๋ค
py manage.py shell
ํ์ฌ ์ฌ์ฉ์ค์ธ DB
๋ฐ์ดํฐ๋ฅผ ์ด์ฉํ์ฌ ํ
์คํธ ํ๊ธฐ ์ํด ๋ค์๊ณผ ๊ฐ์ด setup_test_environment
๋ผ๋ ๋ช
๋ น์ด๋ฅผ ์
๋ ฅํด์ค๋ค.
>>> from django.test.utils import setup_test_environment
>>> setup_test_environment()
๋ค์์ผ๋ก ํ
์คํธ ํด๋ผ์ด์ธํธ ํด๋์ค์ธ Client
๋ฅผ import
ํด์ค๋ค.
์ด๋ client
๊ฐ ํ
์คํธ ์ฝ๋์์ ์ฌ์ฉ์ ์ญํ ์ ํ๋ค.
>>> from django.test import Client
>>> # create an instance of the client for our use
>>> 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/1/">What's up?</a></li>\n \n </ul>\n\n'
>>> response.context['latest_question_list']
<QuerySet [<Question: What's up?>]>
์ฝ๋๋ฅผ ํด์ํด๋ณด์๋ฉด ๋ค์๊ณผ ๊ฐ๋ค.
response = client.get('/')
: ์กฐํ๋ฅผ ์ํด ์์ฒญํ๋ ๋ฐฉ์์ธ get์ ํตํด ํด๋ผ์ด์ธํธ๊ฐ ์นํ์ด์ง๋ฅผ ํธ์ถresponse.status_code
: ์ํ์ฝ๋(status_code)
์๋ฒ์์ ์๋ต์ ๋ํ ์ํ๋ฅผ ์ฝ๋๋ก ์ ๋ฌ, ์ฑ๊ณตํ๋ฉด 200์ ๋ฐํresponse = client.get(reverse('polls:index'))
: ํ
์คํธ ํด๋ผ์ด์ธํธ๊ฐ polls
์ฑ์ index
๋ฅผ get
๋ฐฉ์์ผ๋ก ํธ์ถํ์ฌ ๊ฒฐ๊ณผ๊ฐ์ ๋ฐํresponse.status_code
: ๊ฒฐ๊ณผ๊ฐ์ ์ํ์ฝ๋ ๋ฐํresponse.content
: ๊ฒฐ๊ณผ๊ฐ์ content
๋ฅผ ๋ฐํresponse.context['latest_question_list']
: ๊ฒฐ๊ณผ๊ฐ์ context
๋ฅผ ๋ฐํ์ด๋ ๊ฒ ํ
์คํธ ์ฝ๋๋ฅผ ํตํด์ response
๊ฐ์ด ์ํ๋ ๊ฐ์ด ๋์ค๋์ง ํ์ธํ ์ ์๋ค.
์์ง question
๋ชฉ๋ก(ListView)
์๋ ๋ฏธ๋์ ์์ฑ๋ ์ง๊ฐ ์๋ question
์ด ํ์๋๋ค. ์ด๊ฒ์ ์์ ํด๋ณด๋๋ก ํ๊ฒ ๋ค.
- polls/views.py
from django.utils import timezone
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]
์ฌ๊ธฐ์ __lte
๋ less than equal
๋ก ์ฅ๊ณ ์์ ์ ๊ณตํ๋ ํํฐ์กฐ๊ฑด์ด๋ค.
ํํฐ๋ง์ ํตํด ํ์ฌ๋ ์ง๋ณด๋ค ์๊ฑฐ๋ ๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ฒ ๋๋ค.
์ด์ shell
์ด ์๋ ์ค์ ๋ก ๋ทฐ ํ
์คํธ๋ฅผ ๋ง๋ค์ด๋ณด๋ ค๊ณ ํ๋ค.
polls/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],
)
๊ฐ ํ ์คํธ์ผ์ด์ค๋ฅผ ์์ธํ ์ดํด๋ณด์๋ฉด, ๋ค์๊ณผ ๊ฐ๋ค
test_no_questions
๋ฐ์ดํฐ๊ฐ ์๋ ๊ฒฝ์ฐ๋ฅผ ํ
์คํธ ํ๋ฉฐ, ์ฌ์ฉ๊ฐ๋ฅํ ํฌํ๊ฐ ์์ต๋๋ค
๋ผ๋ ๋ฉ์์ง ๋ฐ latest_question_list
๊ฐ ๋น์ด ์์์ ํ์ธํ๋ค.
test_past_question
๋ฐ์ดํฐ๊ฐ ๊ณผ๊ฑฐ์ธ ๊ฒฝ์ฐ๋ฅผ ํ
์คํธํ๋ค.
๊ณผ๊ฑฐ๋ฐ์ดํฐ๋ฅผ ํ๋ ๋ง๋ค๊ณ ํธ์ถํด์ค ๋ค ๋ฐ์ดํฐ๊ฐ ๋์ค๋์ง ํ์ธํ๋ค.
test_future_question
๋ฐ์ดํฐ๊ฐ ๋ฏธ๋์ธ ๊ฒฝ์ฐ๋ฅผ ํ
์คํธํ๋ค.
๋ฏธ๋๋ฐ์ดํฐ๋ฅผ ํ๋ ๋ง๋ค๊ณ ๋ฐ์ดํฐ๊ฐ ์๋์ค๋์ง ํ์ธํ๋ค.
test_future_question_and_past_question
๊ณผ๊ฑฐ๋ฐ์ดํฐ, ๋ฏธ๋๋ฐ์ดํฐ ํ๋์ฉ ๋ง๋ค๊ณ ๊ณผ๊ฑฐ๋ฐ์ดํฐ๋ง ๋์ค๋์ง ํ์ธํ๋ค.
test_two_past_questions
๊ณผ๊ฑฐ๋ฐ์ดํฐ ๋๊ฐ๋ฅผ ๋ง๋ค๊ณ ๋๊ฐ์ ๋ฐ์ดํฐ๊ฐ ์ ๋์ค๋์ง ํ์ธํ๋ค.
Detail
๋ทฐ ํ
์คํธ ์ถ๊ฐํ๊ธฐ๋ฏธ๋์ question๋ค์ด ๋ชฉ๋ก์ ๋ํ๋์ง ์์ง๋ง, ์ฌ์ฉ์๊ฐ url์ ์๊ณ ์๊ฑฐ๋, ์ถ์ธกํ๋ฉด ์ ๊ทผํ ์ ์๊ธฐ ๋๋ฌธ์ detailview
์ ํ
์คํธ๋ฅผ ์ถ๊ฐํด์ค ์ ์๋ค.
- polls/views.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())
- polls/tests.py
class QuestionDetailViewTests(TestCase):
def test_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)
def test_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)
๐ ์ฐธ๊ณ
๋์ฅ๊ณ ๊ณต์๋ฌธ์
๋์ฅ๊ณ ๊ณต์๋ฌธ์ ๊ฐ์์๋ฃ