Django 입문 test사용

1c2·2023년 7월 24일
0

Django

목록 보기
9/12

코드에서 테스트를 사용하는 이유

  • 시간 절약
  • 문제 예방
  • 코드를 매력적으로 만듦
  • 협업을 위해

테스트 전략

  • 테스트 주도 개발
    코드를 만들기 전에 테스트 코드를 먼저 작성하고 시작

첫번째 테스트 작성

미래에 생성 날짜를 가진 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개

성공적으로 테스트를 통과한다.

DetailView 테스트

모든 테스트를 통과하였다. 그러나 목록에는 나타나지 않는 미래의 질문에 대해서 사용자가 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_questiontest_future_question_and_past_question에 대해서 실패를 하는 모습을 보여주고 있고, 새로운 테스트 케이스에 대해서는 성공을 하였다.

0개의 댓글