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