❗️ django tutorial를 보고 실습 내용을 정리한 글입니다.
잘못된 부분이 있다면 알려주세요 🙏
쉽고 안정적으로 테스트를 하기 위해서는 수정사항이 발생할 때마다 자동으로 실행되는 테스트를 작성하는 것이다. 장고에서 지원하는 테스트 프레임워크를 사용하여 테스트를 자동화해보자.
테스트의 종류는 수없이 많지만 가장 중요한 방법은 아래와 같다.
Unit tests (유닛 테스트)
컴포넌트 단위의 기능적인 동작을 검증. 흔히 class나 function 레벨로 수행.
Regression tests ( 버그수정 확인 테스트 )
기존에 있었던 버그들이 재발하는지 테스트. 이전에 발생했던 버그가 수정되었는지 체크한 이후에, 버그 수정으로 인해 새롭게 발생되는 버그가 없는지 확인차 재수행하게 됨.
Integration tests ( 통합 테스트 )
유닛 테스트를 완료한 각각의 독립적인 컴포넌트들이 함께 결합되어 수행하는 동작을 검증. 컴포넌트간에 요구되는 상호작용을 검사. 유닛 테스트를 통해 컴포넌트의 내부적인 동작은 이미 검증했으므로 내부 동작에 대해서는 테스트 할 필요 없다.
polls/models.py
에는 버그가 하나 있다. Question.was_published_recently()
는 최근(1일) 게시된 경우 값을 불러와야하는데 pub_date
가 미래로 설정되어있을 경우에도 불러온다.
shell
에서 아래와 같이 테스트를 하면 미래의 시간이 True
로 반영되는 것을 볼 수 있다.
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
이 아닌 파이썬 코드로 다시 작성해야한다.
# 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)
python manage.py test polls # AssertionError: True is not False
test
뒤에 앱이름을 쓰면 장고에서 알아서 polls/tests.py
를 실행시켜준다. tests.py
파일을 찾는 과정은 앱 아래에 있는 파일 중 django.test.TestCase
의 서브 클래스를 찾는다. QuestionModelTests
가 TestCase
를 상속 받았으니 서브 클래스가 된다. 테스트한 결과 True
가 나왔다는 버그를 알려준다. 또한 내부적으로 테스트 목적으로 따로 데이터베이스도 만든다. 왜냐하면 테스트를 진행하는데 데이터베이스에 값을 넣거나 수정해야하는 경우가 있다. 이럴 경우 실제 서비스되고 있는 데이터베이스에서 수행할 수 없기 때문이다.
Question.was_published_recently()
는 pub_date
가 과거일 때만 True
를 반환하도록 수정하면 된다.
# polls/models.py
def was_published_recently(self):
now = timezone.now()
return now - datetime.timedelta(days=1) <= self.pub_date <= now
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)
was_published_recently()
만 써도 확인이 가능하지만, 총 3가지의 테스트를 사용하여 조금 더 정확하게 값을 검증한다. 미래, 현재, 과거에 대한 검증을 모두 한다.
다시 test
를 실행하면 테스트가 성공한 것을 볼 수 있다.
장고는 뷰 레벨에서 코드와 사용자가 상호작용하는 것을 시뮬레이션하기 위해 테스트 클라이언트 클래스 Client
를 제공한다. Client
클래스를 사용하면 직접 홈페이지를 사용하는것처럼 느껴진다.
polls/views.py
파일도 pub_date
가 미래인 경우 표시가 된다.
# 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]
Question.objects.filter (pub_date__lte = timezone.now ())
는 timezone.now
보다 pub_date
작거나 같은 경우 Question queryset
을 반환한다.
수정한 뷰를 테스트 클래스를 작성하여 테스트를 해봐야한다.
# polls/tests.py
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
질문을 생성한다.
test_no_questions
질문이 없을 경우 메시지와 함께 latest_question_list
가 비어있음을 확인한다.
test_past_question
질문을 생성하고 그 질문이 출력되는지 확인한다.
test_future_question
에서 pub_date
가 미래인 질문을 만들고 출력이 안 되는지 확인한다.