Django Automated Testing

자동화된 테스트는 앞서서 shell 을 사용해 메소드의 동작을 검사하거나 데이터를 입력해서 테스트 한것과 다르지 않다. 차이점은 테스트 작업이 시스템에서 수행된다는 점이다. 한번 테스트 세트를 작성한 후에는 앱을 변경할 때 수동 테스트를 수행하지 않아도 원래 의도대로 코드가 작동하는지 확인할 수 있다.

  • 자동화된 테스트를 통해 시간을 절약할 수 있다. 테스트를 작성하는 작업은 어플리케이션을 수동으로 테스트하거나 새로 발견된 문제의 원인을 확인하는 데 많은 시간을 투자하는 것보다 훨씬 더 효과적입니다.

  • 문제를 식별하는 것이 아니라 예방할 수 있다.

애플리케이션 테스트는 일반적으로 <app_name>/tests.py 파일에 있다. 테스트 시스템은 test로 시작하는 메소드를 자동으로 찾는다.

Model Test

# polls/models.py

class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')
    def __str__(self):
        return '%s >> %s' % (self.question_text, self.pub_date)
    def was_published_recently(self):
        return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
# polls/test.py
import datetime

from django.utils import timezone
from django.test import TestCase

from .models import Question

# Create your tests here.
class QuestionMdoelTests(TestCase):
    def test_was_published_recently_with_future_question(self):
        time = timezone.now() + datetime.timedelta(days=30)
        future_question = Question(pub_date=time)

self.assertIs(future_question.was_published_recently(),False)

다음은 간단한 예시이다. 지금 시간보다 30일 이후의 Question을 생성한 뒤 최근에 생성한게 맞는지 확인하는 것이다.

$ python manage.py test <app_name>
$ python manage.py test polls
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
F
======================================================================
FAIL: test_was_published_recently_with_future_question (polls.tests.QuestionMdoelTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/jeongdaye/Documents/study/test_app/mysite2/polls/tests.py", line 13, 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.001s

FAILED (failures=1)
Destroying test database for alias 'default'...

우리는 False가 return되기를 바라는데 True가 반환된다는 것을 발견할 수 있다.

# Create your models here.
class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')
    def __str__(self):
        return '%s >> %s' % (self.question_text, self.pub_date)
    def was_published_recently(self):
        now = timezone.now()
        return now - datetime.timedelta(days=1) <= self.pub_date <= now

다음과 같이 was_published_recently() 를 수정해주고 테스트 명령어를 실행해보면 테스트를 통과한 것을 확인할 수 있다.

Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK
Destroying test database for alias 'default'...

View Test

Shell에 테스트 환경 구성하기

$ python manage.py shell
Python 3.7.2 (default, Mar  5 2019, 16:08:31) 
[Clang 10.0.0 (clang-1000.10.44.4)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>>
>>> from django.test.utils import setup_test_environment
>>> setup_test_environment()

이 메소드는 테스트 데이터베이스를 설정하지 않기 때문에 현재 사용중인 데이터베이스 위에서 돌게되며 결과는 데이터베이스의 데이터에 따라 다르게 나온다.

>>> from django.test import Client
>>> client = Client()

테스트 클라이언트를 생성하여 작업을 수행할 수 있다.

>>> from django.test.utils import setup_test_environment
>>> setup_test_environment()
>>> from django.test import Client
>>> client = Client()
>>> from django.urls import reverse
>>> response = client.get(reverse('polls:index'))
>>> response.status_code
200
>>> response.content
b'\n    <ul>\n    \n        <li><a href="/polls/2/">Are you happy?</a></li>\n    \n        <li><a href="/polls/1/">What&#39;s your name?</a></li>\n    \n    </ul>\n'
>>> response.context['latest_question_list']
<QuerySet [<Question: Are you happy? >> 2019-04-05 05:54:39>, <Question: What's your name? >> 2019-04-04 06:21:55.323284>]>
>>>
def create_question(question_text, days):
    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):
        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):
        create_question(question_text="Past question.", days=-30)
        response = self.client.get(reverse('polls:index'))
        self.assertQuerysetEqual(
            response.context['latest_question_list'],
            ['<Question: Past question.>']
        )

    def test_future_question(self):
        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):
        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: Past question.>']
        )

    def test_two_past_questions(self):
        create_question(question_text="Past question 1.", days=-30)
        create_question(question_text="Past question 2.", days=-5)
        response = self.client.get(reverse('polls:index'))
        self.assertQuerysetEqual(
            response.context['latest_question_list'],
            ['<Question: Past question 2.>', '<Question: Past question 1.>']
        )

class QuestionDetailViewTests(TestCase):
    def test_future_question(self):
        future_question = create_question(question_text='Future question.', days=5)
        url = reverse('polls:detail', args=(future_question.id,))
        response = self.client.get(url)
        self.assertEqual(response.status_code, 404)

    def test_past_question(self):
        past_question = create_question(question_text='Past Question.', days=-5)
        url = reverse('polls:detail', args=(past_question.id,))
        response = self.client.get(url)
        self.assertContains(response, past_question.question_text)

다음과 같이 View에 대해서도 테스트 코드를 작성할 수 있다. 테스트를 할 때는 많이 할 수록 좋다.

Last updated