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