백엔드/Django

[Django] Document 따라 읽기(6) - Model 테스트

안용감한호랑이 2023. 11. 14. 00:32

 

[백엔드/Django] - [Django] Document 따라 읽기(5) - 앱 작성(2)

 

[Django] Document 따라 읽기(5) - 앱 작성(2)

[전체 보기] - [Django] Document 따라 읽기(4) - 앱 작성(1) [Django] Document 따라 읽기(4) - 앱 작성(1) [백엔드/Django] - [Django] Document 따라 읽기(3) - admin 페이지 [Django] Document 따라 읽기(3) - admin 페이지 [백엔

dog-foot-writen.tistory.com


테스트를 생성해야 하는 이유

 

시간 절약

지금까지 생성한 설문조사 애플리케이션은 매우 단순하지만 보다 정교하고 복잡한 애플리케이션에서는 구성 요소 간에 수십 개의 복잡한 상호 작용이 있을 수 있습니다. 이러한 구성 요소가 변경되면 응용 프로그램 동작에 예상치 못한 결과가 발생할 수 있어 여러 테스트 코드를 통해 기능에 문제가 발생하지 않았는지 확인할 수 있다.  

What is TDD?

 

테스트 주도 개발 - 나무위키

테스트가 실제로 거쳐가는 코드가 몇 줄인지 확인하는 용도로 쓴다. 예외 처리 같은 것이 제대로 테스팅 되는지 확인하거나, 아니면 다른 개발자들이 제대로 테스팅 코드를 작성하는지 확인하

namu.wiki

 

문제 예방

테스트를 통해 의도된 동작들이 제대로 수행되는지 미리 테스트해 볼 수 있으며 이것은 자기 자신의 코드가 어떤 동작을 하는지 알기 위해 확인하는 과정이다.

 

 

팀 내 협력

단일 개발자가 아니라면 복잡한 애플리케이션에서 유지 관리하는 것은 어려운 일이지만 테스트를 통해 팀 내 동료 혹은 내가 코드를 손상시키지 않도록 보장해 준다.

TDD에 대해서는 이후 새로운 게시글로 심도 있게 작성하겠습니다.

 

 

 

 

첫 번째 테스트 작성하기

버그 식별

이전 글에서 생성한 polls에는 오류가 있습니다.

Question.was_published_recently() 메서드에서 마지막날 내에 Question이 게시되었지만 Question의 pub_date필드가 미래의 값이 입력되는 경우에도 True를 반환하게 됩니다.

python 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

polls의 Question에 구현된 was_published_recently는 1일 이내에 게시되었는지 확인하는 목적의 함수였지만 위 코드처럼 30일을 더해주어도 True를 반환하고 있습니다.

 

테스트 코드 작성

Django에서 테스트를 위한 기본 경로는 응용프로그램의 tests.py 파일에 있습니다. 따라서 shell을 통해 자동 테스트를 수행할 수 있도록 tests.py를 작성하겠습니다.

# 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에서 수행한 것과 동일하지만 self.assertIs를 통해 테스트를 구현하였습니다.

 

python manage.py test polls

수행되는 작업은 다음과 같습니다.

  1. manage.py에 test "애플리케이션명"을 통해 polls의 django.test.TestCase class를 찾습니다.
  2. 테스트를 위한 데이터베이스를 생성합니다.
  3. test로 시작하는 test method들을 찾습니다.
  4. test_was_published_recently_with_future_question에서 Question에 30일을 더한 값을 pub_date에 할당합니다.
  5. assertIs() 메서드가 was_published_recently()를 수행하고, True를 반환하지만 제가 지정한 False와 다르기 때문에 테스트를 실패합니다.

manage.py : 테스트를 실패한 결과

 

버그 수정

이미 어느 곳에서 에러가 발생하는지 알고 있기 때문에 해당 코드를 날짜가 과거인 경우에만 반환되도록 메서드를 수정하도록 하겠습니다.

# polls/models.py

def was_published_recently(self):
    now = timezone.now()
    return now - datetime.timedelta(days=1) <= self.pub_date <= now

다시 테스트를 진행한다면 테스트가 에러 없이 정상적으로 수행되는 것을 알 수 있습니다.

 

포괄적인 테스트

동일한 클래스에 대해 테스트 메서드를 여러 개 추가함으로써 해당 메서드를 더 포괄적으로 테스트할 수 있습니다.

was_published_recently()를 과거, 최근, 미래 질문에 대해 합리적인 값을 반환하는지 확인하도록 테스트 코드를 추가하겠습니다.

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)