Django 사용해보기 - 3

Sawol·2021년 5월 14일
0

django

목록 보기
3/5
post-thumbnail

❗️ 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의 서브 클래스를 찾는다. QuestionModelTestsTestCase를 상속 받았으니 서브 클래스가 된다. 테스트한 결과 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가 미래인 질문을 만들고 출력이 안 되는지 확인한다.

0개의 댓글