Django 08편 - TDD 1편
포스트
취소

Django 08편 - TDD 1편

들어가기 전…

위 프로젝트에서 다룬 앱을 이용해 TDD를 구성해보자.

테스트 주도 개발(Test-driven development, TDD)이란?

테스트 주도 개발(Test-driven development TDD)은 매우 짧은 개발 사이클을 반복하는 소프트웨어 개발 프로세스 중 하나이다. 개발자는 먼저 요구사항을 검증하는 자동화된 테스트 케이스를 작성한다. 그런 후에, 그 테스트 케이스를 통과하기 위한 최소한의 코드를 생성한다. 마지막으로 작성한 코드를 표준에 맞도록 리팩토링한다.

  • 자동화된 테스트란? 테스트는 다양한 수준에서 작동합니다. 일부 테스트는 작은 세부 사항에 적용될 수 있습니다 (특정 모델 메서드는 예상대로 값을 반환합니까?) 또 다른 테스트는 소프트웨어의 전반적인 작동을 검사합니다 (사이트에서 사용자 입력 시퀀스가 원하는 결과를 생성합니까?). 자동화 된 테스트에서는 테스트 작업이 시스템에서 수행됩니다. 한 번 테스트 세트를 작성한 이후에는 앱을 변경할 때 수동 테스트를 수행하지 않아도 원래 의도대로 코드가 작동하는지 확인할 수 있습니다.
  • 테스트를 만들어야하는 이유
    1. 테스트를 통해 시간을 절약 할 수 있습니다. 더 정교한 어플리케이션에서는 구성 요소간에 수십 개의 복잡한 상호 작용이있을 수 있습니다. 이러한 구성 요소를 변경하면 응용 프로그램의 동작에 예기치 않은 결과가 발생할 수 있습니다. 이를 확인하기 위한 수동 테스트 작업을 자동화된 테스트가 몇초만에 해낼수 있다면 귀한시간을 많이 아낄수 있겠죠?. 무언가가 잘못되어도 테스트를 통해 예기치 않은 동작을 일으키는 코드를 식별하는 데 도움이됩니다.
    2. 테스트는 문제를 그저 식별만하는 것이 아니라 예방합니다. 테스트가 없으면 어플리케이션의 목적 또는 의도 된 동작이 다소 불투명 할 수 있습니다. 심지어 자신의 코드 일 때도, 정확히 무엇을하고 있는지 알아 내려고 노력하게 됩니다. 테스트는 이 불투명함을 바꿉니다. 그들은 내부에서 코드를 밝혀 내고, 어떤 것이 잘못 될 때, 그것이 잘못되었다는 것을 깨닫지 못했다고 할지라도, 잘못된 부분에 빛을 집중시킵니다.
    3. 테스트가 코드를 더 매력적으로 만듭니다. 테스트 작성을 시작해야하는 또다른 이유는 다른 개발자들이 당신의 소프트웨어를 사용하는것을 진지하게 고려하기 전에 테스트 코드를 보기를 원하기 때문입니다.
    4. 테스트는 협업을 원활하게 해줍니다. 복잡한 애플리케이션은 팀별로 유지 관리됩니다. 테스트는 동료가 실수로 코드를 손상시키지 않는다는 것을 보증합니다 (그리고 당신이 동료의 코드를 모르는새에 망가트리는것도).

첫 번째 테스트 작성하기

이전에 만든 polls 앱에는 버그가 있다. Question.was_published_recently() 메소드는 Question이 어제 게시된 경우 True를 반환(올바른 동작)할 뿐만 아니라 Question의 pub_date 필드가 미래로 설정되어 있을 때도 그렇다(틀린 동작).

  • 버그를 노출하는 테스트 코드 만들기

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    import datetime
    
    from django.test import TestCase
    from django.utils import timezone
    
    from .models import Question
    
    
    # Create your tests here.
    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)
    

    미래의 pub_date를 가진 Question 인스턴스를 생성하는 메소드를 가진 django.test.TestCase 하위 클래스를 생성했다. 그런 다음 was_published_recently()의 출력이 False가 되는지 확인했다.

  • 테스트 실행
    1
    
    python manage.py test apps.polls
    
  • 테스트 결과

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
    Creating test database for alias 'default'...
    System check identified no issues (0 silenced).
    F
    ======================================================================
    FAIL: test_was_published_recently_with_future_question (apps.polls.tests.QuestionModelTests)
    was_published_recently() returns False for questions whose pub_date
    ----------------------------------------------------------------------
    Traceback (most recent call last):
    File "C:\Users\-\Desktop\dev\github\django-demo\django_demo\apps\polls\tests.py", line 19, 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.006s
    
    FAILED (failures=1)
    Destroying test database for alias 'default'...
    

    혹시 테스트 결과가 ‘AssertionError: True is not False’가 아니라 ‘ModuleNotFoundError: No module named ~’이 나온다면 파일 구조가 아래와 같은지 확인해본다.

    사진

    특히, apps 폴더 아래에 __init__.py가 있는지 확인한다. 해당 파일이 존재하지않으면 파이썬 패키지로 인식하지 않는다.

버그 수정하기

  • model.py
    1
    2
    3
    4
    
        def was_published_recently(self):
            # return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
            now = timezone.now()
            return now - datetime.timedelta(days=1) <= self.pub_date <= now
    

    Question.was_published_recently()는 pub_date가 미래에 있다면 False를 반환해야 한다. models.py에서 날짜가 과거에 있을 때에만 True를 반환하도록 메소드를 수정하자.

  • 다시 테스트 실행하기
    1
    
    python manage.py test apps.polls
    
  • 테스트 결과

    1
    2
    3
    4
    5
    6
    7
    8
    
    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'...
    

    테스트가 무사히 통과되었다. X)

보다 포괄적인 테스트

  • tests.py

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
    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)
    

    위 코드를 추가해주면 이제 Question.was_published_recently()가 과거, 최근, 미래의 질문에 대해 올바른 값을 반환한다는걸 확인시켜주는 세가지 테스트를 수행할 수 있다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.