자동화된 테스트 생성

Beginner

소개

이 튜토리얼은 **Form Processing and Cutting Down Our Code**에서 시작합니다. 웹 설문 애플리케이션을 구축했으며, 이제 이에 대한 자동화된 테스트를 생성할 것입니다.

자동화된 테스트 소개

자동화된 테스트란 무엇인가요?

테스트는 코드의 작동을 확인하는 루틴입니다.

테스트는 다양한 수준에서 작동합니다. 일부 테스트는 작은 세부 사항 (특정 모델 메서드가 예상대로 값을 반환하는가?) 에 적용될 수 있으며, 다른 테스트는 소프트웨어의 전반적인 작동을 검사합니다 (사이트에서 일련의 사용자 입력이 원하는 결과를 생성하는가?). 이는 **Set Up the Database**에서 메서드의 동작을 검사하기 위해 shell을 사용하거나, 애플리케이션을 실행하고 데이터를 입력하여 동작 방식을 확인했던 것과 다르지 않습니다.

자동화된 테스트에서 다른 점은 테스트 작업이 시스템에 의해 자동으로 수행된다는 것입니다. 테스트 세트를 한 번 생성하면, 앱을 변경할 때마다 시간 소모적인 수동 테스트를 수행하지 않고도 코드가 원래 의도한 대로 계속 작동하는지 확인할 수 있습니다.

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

그렇다면 왜 테스트를 생성해야 하고, 왜 지금 해야 할까요?

Python/Django를 배우는 것만으로도 충분하다고 느낄 수 있으며, 또 다른 것을 배우고 해야 한다는 것이 압도적이고 불필요하게 느껴질 수 있습니다. 결국, 우리의 설문 애플리케이션은 현재 잘 작동하고 있습니다. 자동화된 테스트를 생성하는 번거로움을 겪는다고 해서 더 잘 작동하게 되는 것은 아닙니다. 설문 애플리케이션을 만드는 것이 Django 프로그래밍의 마지막 작업이라면, 자동화된 테스트를 생성하는 방법을 알 필요는 없습니다. 하지만 그렇지 않다면, 지금이 배우기에 좋은 시기입니다.

테스트는 시간을 절약해 줍니다.

어느 시점까지는 '작동하는 것처럼 보이는지 확인'하는 것이 만족스러운 테스트가 될 것입니다. 더 정교한 애플리케이션에서는 구성 요소 간에 수십 가지의 복잡한 상호 작용이 있을 수 있습니다.

이러한 구성 요소 중 하나를 변경하면 애플리케이션 동작에 예상치 못한 결과가 발생할 수 있습니다. 여전히 '작동하는 것처럼 보이는지' 확인하는 것은 테스트 데이터의 스무 가지 다른 변형으로 코드의 기능을 실행하여 무언가를 망치지 않았는지 확인하는 것을 의미할 수 있습니다. 이는 시간을 잘 활용하는 방법이 아닙니다.

자동화된 테스트가 몇 초 만에 이 작업을 수행할 수 있다면 특히 그렇습니다. 문제가 발생한 경우, 테스트는 예상치 못한 동작을 유발하는 코드를 식별하는 데에도 도움이 됩니다.

때로는 생산적이고 창의적인 프로그래밍 작업에서 벗어나 테스트를 작성하는 지루하고 흥미롭지 않은 작업에 직면하는 것이 귀찮게 느껴질 수 있습니다. 특히 코드가 제대로 작동한다는 것을 알고 있을 때 더욱 그렇습니다.

그러나 테스트를 작성하는 작업은 애플리케이션을 수동으로 테스트하거나 새로 도입된 문제의 원인을 식별하는 데 시간을 보내는 것보다 훨씬 더 만족스럽습니다.

테스트는 문제를 식별할 뿐만 아니라 예방합니다.

테스트를 단순히 개발의 부정적인 측면으로 생각하는 것은 실수입니다.

테스트가 없으면 애플리케이션의 목적이나 의도된 동작이 다소 불투명할 수 있습니다. 자신의 코드일 때조차도, 때로는 코드가 정확히 무엇을 하고 있는지 알아내기 위해 코드를 뒤적거리는 자신을 발견하게 될 것입니다.

테스트는 이를 변경합니다. 테스트는 코드 내부에서 빛을 비추고, 무언가 잘못되면, 심지어 잘못되었다는 것을 깨닫지 못했을 때조차도 잘못된 부분에 빛을 집중시킵니다.

테스트는 코드를 더 매력적으로 만듭니다.

훌륭한 소프트웨어를 만들었을지라도, 테스트가 없기 때문에 다른 많은 개발자들이 이를 보기를 거부할 것입니다. 테스트가 없으면 신뢰하지 않을 것입니다. Django 의 원래 개발자 중 한 명인 Jacob Kaplan-Moss 는 "테스트가 없는 코드는 설계상 깨진 것입니다."라고 말합니다.

다른 개발자들이 소프트웨어를 진지하게 받아들이기 전에 테스트를 보기를 원한다는 것은 테스트를 작성하기 시작해야 하는 또 다른 이유입니다.

테스트는 팀의 협업을 돕습니다.

이전 내용은 애플리케이션을 유지 관리하는 단일 개발자의 관점에서 작성되었습니다. 복잡한 애플리케이션은 팀에서 유지 관리합니다. 테스트는 동료가 실수로 코드를 망치지 않도록 보장합니다 (그리고 여러분이 그들의 코드를 모르게 망치지 않도록). Django 프로그래머로 생계를 유지하고 싶다면, 테스트를 잘 작성해야 합니다!

기본적인 테스트 전략

테스트 작성을 위한 다양한 접근 방식이 있습니다.

일부 프로그래머는 "테스트 주도 개발"이라는 규율을 따릅니다. 실제로 코드를 작성하기 전에 테스트를 작성합니다. 이는 직관적이지 않게 보일 수 있지만, 사실 대부분의 사람들이 종종 하는 것과 유사합니다. 즉, 문제를 설명한 다음, 이를 해결하기 위한 코드를 생성합니다. 테스트 주도 개발은 문제를 Python 테스트 케이스로 공식화합니다.

더 자주, 테스트를 처음 접하는 사람은 코드를 작성한 다음, 나중에 테스트가 있어야 한다고 결정합니다. 아마도 더 일찍 테스트를 작성하는 것이 더 좋았을 수 있지만, 시작하기에 너무 늦은 때는 없습니다.

때로는 테스트 작성을 어디서부터 시작해야 할지 파악하기 어려울 수 있습니다. 수천 줄의 Python 코드를 작성했다면, 테스트할 대상을 선택하는 것이 쉽지 않을 수 있습니다. 이러한 경우, 새로운 기능을 추가하거나 버그를 수정할 때마다 다음에 변경할 때 첫 번째 테스트를 작성하는 것이 유용합니다.

그러니 바로 그렇게 해 봅시다.

첫 번째 테스트 작성

버그를 식별합니다.

다행히, polls 애플리케이션에 바로 수정할 수 있는 작은 버그가 있습니다. Question.was_published_recently() 메서드는 Question이 지난 하루 이내에 게시된 경우 (정확함) 와 Questionpub_date 필드가 미래에 있는 경우 (확실히 그렇지 않음) 모두 True를 반환합니다.

shell을 사용하여 날짜가 미래에 있는 질문에 대한 메서드를 확인하여 버그를 확인합니다.

cd ~/project/mysite
python manage.py shell
>>> import datetime
>>> from django.utils import timezone
>>> from polls.models import Question
>>> ## create a Question instance with pub_date 30 days in the future
>>> future_question = Question(pub_date=timezone.now() + datetime.timedelta(days=30))
>>> ## was it published recently?
>>> future_question.was_published_recently()
True

미래의 것은 '최근'이 아니므로, 이는 명백히 잘못된 것입니다.

버그를 노출하는 테스트 생성

문제에 대해 테스트하기 위해 shell에서 방금 수행한 작업은 자동화된 테스트에서 수행할 수 있는 정확한 작업이므로, 이를 자동화된 테스트로 바꿔보겠습니다.

애플리케이션 테스트를 위한 일반적인 위치는 애플리케이션의 tests.py 파일입니다. 테스트 시스템은 이름이 test로 시작하는 모든 파일에서 테스트를 자동으로 찾습니다.

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)

여기서는 미래의 pub_date를 가진 Question 인스턴스를 생성하는 메서드가 있는 django.test.TestCase 서브클래스를 만들었습니다. 그런 다음 was_published_recently()의 출력을 확인합니다. 이는 False여야 합니다.

테스트 실행

터미널에서 테스트를 실행할 수 있습니다.

python manage.py test polls

다음과 같은 내용이 표시됩니다.

[object Object]

다른 오류가 발생했나요?

여기서 NameError가 발생한다면, Part 2 <tutorial02-import-timezone>에서 datetimetimezone의 import 를 polls/models.py에 추가하는 단계를 놓쳤을 수 있습니다. 해당 섹션에서 import 를 복사하여 테스트를 다시 실행해 보세요.

발생한 일은 다음과 같습니다.

  • manage.py test pollspolls 애플리케이션에서 테스트를 찾았습니다.
  • django.test.TestCase 클래스의 서브클래스를 찾았습니다.
  • 테스트 목적으로 특별한 데이터베이스를 생성했습니다.
  • 테스트 메서드 (이름이 test로 시작하는 메서드) 를 찾았습니다.
  • test_was_published_recently_with_future_question에서 pub_date 필드가 30 일 후인 Question 인스턴스를 생성했습니다.
  • ... 그리고 assertIs() 메서드를 사용하여 was_published_recently()True를 반환한다는 것을 발견했습니다. 하지만 False를 반환하기를 원했습니다.

테스트는 어떤 테스트가 실패했는지, 심지어 실패가 발생한 줄까지 알려줍니다.

버그 수정

우리는 이미 문제가 무엇인지 알고 있습니다. Question.was_published_recently()pub_date가 미래에 있는 경우 False를 반환해야 합니다. models.py에서 메서드를 수정하여 날짜가 과거에 있는 경우에만 True를 반환하도록 합니다.

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

그리고 테스트를 다시 실행합니다.

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'...

버그를 식별한 후, 이를 노출하는 테스트를 작성하고 테스트가 통과하도록 코드의 버그를 수정했습니다.

미래에 우리 애플리케이션에서 다른 많은 문제가 발생할 수 있지만, 이 버그를 실수로 다시 도입하지 않을 수 있습니다. 테스트를 실행하면 즉시 경고를 받기 때문입니다. 우리는 이 애플리케이션의 작은 부분을 영원히 안전하게 고정했다고 간주할 수 있습니다.

더 포괄적인 테스트

여기서 우리는 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)

이제 Question.was_published_recently()가 과거, 최근, 미래의 질문에 대해 합리적인 값을 반환하는지 확인하는 세 개의 테스트가 있습니다.

다시 말하지만, polls는 최소한의 애플리케이션이지만, 미래에 얼마나 복잡해지든, 다른 코드와 상호 작용하든, 이제 테스트한 메서드가 예상대로 동작한다는 보장을 어느 정도 갖게 되었습니다.

뷰 테스트

polls 애플리케이션은 상당히 무차별적입니다. pub_date 필드가 미래에 있는 질문을 포함하여 모든 질문을 게시합니다. 이를 개선해야 합니다. 미래에 pub_date를 설정하는 것은 해당 시점에 질문이 게시되지만, 그 전까지는 보이지 않음을 의미해야 합니다.

뷰에 대한 테스트

위에서 버그를 수정했을 때, 우리는 먼저 테스트를 작성한 다음, 이를 수정하는 코드를 작성했습니다. 사실 그것은 테스트 주도 개발의 예였지만, 우리가 작업을 어떤 순서로 하든 실제로 중요하지 않습니다.

첫 번째 테스트에서는 코드의 내부 동작에 집중했습니다. 이 테스트에서는 웹 브라우저를 통해 사용자가 경험하는 것처럼 동작을 확인하려고 합니다.

무언가를 수정하기 전에, 우리가 사용할 수 있는 도구를 살펴보겠습니다.

Django 테스트 클라이언트

Django 는 뷰 수준에서 코드와 상호 작용하는 사용자를 시뮬레이션하기 위해 테스트 ~django.test.Client를 제공합니다. tests.py 또는 심지어 shell에서도 사용할 수 있습니다.

shell에서 다시 시작합니다. 여기서는 tests.py에서 필요하지 않은 몇 가지 작업을 수행해야 합니다. 첫 번째는 shell에서 테스트 환경을 설정하는 것입니다.

python manage.py shell
>>> from django.test.utils import setup_test_environment
>>> setup_test_environment()

~django.test.utils.setup_test_environment는 응답에서 response.context와 같이 그렇지 않으면 사용할 수 없는 몇 가지 추가 속성을 검사할 수 있도록 템플릿 렌더러를 설치합니다. 이 메서드는 테스트 데이터베이스를 설정하지 않으므로, 다음은 기존 데이터베이스에 대해 실행되며 이미 생성한 질문에 따라 출력이 약간 다를 수 있습니다. settings.pyTIME_ZONE이 올바르지 않으면 예기치 않은 결과가 발생할 수 있습니다. 이전에 설정했는지 기억나지 않으면, 계속하기 전에 확인하십시오.

다음으로 테스트 클라이언트 클래스를 가져와야 합니다 (나중에 tests.py에서는 자체 클라이언트가 있는 django.test.TestCase 클래스를 사용하므로, 이것은 필요하지 않습니다).

>>> from django.test import Client
>>> ## create an instance of the client for our use
>>> client = Client()

준비가 되면, 클라이언트에게 몇 가지 작업을 요청할 수 있습니다.

>>> ## get a response from '/'
>>> response = client.get("/")
Not Found: /
>>> ## we should expect a 404 from that address; if you instead see an
>>> ## "Invalid HTTP_HOST header" error and a 400 response, you probably
>>> ## omitted the setup_test_environment() call described earlier.
>>> response.status_code
404
>>> ## on the other hand we should expect to find something at '/polls/'
>>> ## we'll use 'reverse()' rather than a hardcoded URL
>>> 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/1/">What&#x27;s up?</a></li>\n    \n    </ul>\n\n'
>>> response.context["latest_question_list"]
<QuerySet [<Question: What's up?>]>

뷰 개선

polls 목록은 아직 게시되지 않은 polls(즉, pub_date가 미래에 있는 polls) 를 표시합니다. 이를 수정해 보겠습니다.

**Form Processing and Cutting Down Our Code**에서 ~django.views.generic.list.ListView를 기반으로 하는 클래스 기반 뷰를 소개했습니다.

class IndexView(generic.ListView):
    template_name = "polls/index.html"
    context_object_name = "latest_question_list"

    def get_queryset(self):
        """Return the last five published questions."""
        return Question.objects.order_by("-pub_date")[:5]

get_queryset() 메서드를 수정하고 timezone.now()와 비교하여 날짜도 확인하도록 변경해야 합니다. 먼저 import 를 추가해야 합니다.

from django.utils import timezone

그런 다음 다음과 같이 get_queryset 메서드를 수정해야 합니다.

def get_queryset(self):
    """
    Return the last five published questions (not including those set to be
    published in the future).
    """
    return Question.objects.filter(pub_date__lte=timezone.now()).order_by("-pub_date")[
        :5
    ]

Question.objects.filter(pub_date__lte=timezone.now())pub_datetimezone.now보다 작거나 같은 (즉, 이전이거나 같은) Question을 포함하는 쿼리셋을 반환합니다.

새로운 뷰 테스트

이제 runserver를 시작하고, 브라우저에서 사이트를 로드하고, 과거와 미래의 날짜로 Question을 생성하고, 게시된 질문만 나열되는지 확인하여 예상대로 동작하는지 확인할 수 있습니다. 이것에 영향을 미칠 수 있는 변경을 할 때마다 매번 그렇게 할 필요는 없으므로, 위의 shell 세션을 기반으로 테스트도 만들어 보겠습니다.

polls/tests.py에 다음을 추가합니다.

from django.urls import reverse

그리고 질문을 생성하는 바로 가기 함수와 새로운 테스트 클래스를 만들겠습니다.

def create_question(question_text, days):
    """
    Create a question with the given `question_text` and published the
    given number of `days` offset to now (negative for questions published
    in the past, positive for questions that have yet to be published).
    """
    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):
        """
        If no questions exist, an appropriate message is displayed.
        """
        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):
        """
        Questions with a pub_date in the past are displayed on the
        index page.
        """
        question = create_question(question_text="Past question.", days=-30)
        response = self.client.get(reverse("polls:index"))
        self.assertQuerySetEqual(
            response.context["latest_question_list"],
            [question],
        )

    def test_future_question(self):
        """
        Questions with a pub_date in the future aren't displayed on
        the index page.
        """
        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):
        """
        Even if both past and future questions exist, only past questions
        are displayed.
        """
        question = 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],
        )

    def test_two_past_questions(self):
        """
        The questions index page may display multiple questions.
        """
        question1 = create_question(question_text="Past question 1.", days=-30)
        question2 = create_question(question_text="Past question 2.", days=-5)
        response = self.client.get(reverse("polls:index"))
        self.assertQuerySetEqual(
            response.context["latest_question_list"],
            [question2, question1],
        )

이 중 일부를 자세히 살펴보겠습니다.

먼저, 질문을 생성하는 프로세스에서 반복을 제거하기 위한 질문 바로 가기 함수인 create_question입니다.

test_no_questions는 질문을 생성하지 않지만, "No polls are available." 메시지를 확인하고 latest_question_list가 비어 있는지 확인합니다. django.test.TestCase 클래스는 몇 가지 추가적인 assertion 메서드를 제공합니다. 이 예제에서는 ~django.test.SimpleTestCase.assertContains()~django.test.TransactionTestCase.assertQuerySetEqual()을 사용합니다.

test_past_question에서는 질문을 생성하고 목록에 나타나는지 확인합니다.

test_future_question에서는 미래의 pub_date를 가진 질문을 생성합니다. 데이터베이스는 각 테스트 메서드마다 재설정되므로, 첫 번째 질문은 더 이상 존재하지 않으며, 따라서 인덱스에도 질문이 없어야 합니다.

등등. 실제로, 우리는 테스트를 사용하여 사이트의 관리자 입력 및 사용자 경험에 대한 이야기를 전달하고, 시스템의 각 상태와 상태의 모든 새로운 변경에 대해 예상되는 결과가 게시되는지 확인하고 있습니다.

DetailView 테스트

우리가 한 일은 잘 작동합니다. 그러나 미래의 질문이 index에 나타나지 않더라도, 사용자는 올바른 URL 을 알고 있거나 추측하면 여전히 해당 질문에 도달할 수 있습니다. 따라서 DetailView에 유사한 제약 조건을 추가해야 합니다.

class DetailView(generic.DetailView):
    ...

    def get_queryset(self):
        """
        Excludes any questions that aren't published yet.
        """
        return Question.objects.filter(pub_date__lte=timezone.now())

그런 다음, pub_date가 과거에 있는 Question이 표시될 수 있고, 미래에 있는 Question은 표시되지 않는지 확인하기 위해 몇 가지 테스트를 추가해야 합니다.

class QuestionDetailViewTests(TestCase):
    def test_future_question(self):
        """
        The detail view of a question with a pub_date in the future
        returns a 404 not found.
        """
        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):
        """
        The detail view of a question with a pub_date in the past
        displays the question's text.
        """
        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)

더 많은 테스트를 위한 아이디어

ResultsView에 유사한 get_queryset 메서드를 추가하고 해당 뷰에 대한 새로운 테스트 클래스를 만들어야 합니다. 방금 생성한 것과 매우 유사할 것입니다. 사실, 많은 반복이 있을 것입니다.

또한, 그 과정에서 테스트를 추가하여 애플리케이션을 다른 방식으로 개선할 수 있습니다. 예를 들어, Choices가 없는 Questions가 사이트에 게시될 수 있다는 것은 어리석습니다. 따라서, 우리의 뷰는 이것을 확인하고, 그러한 Questions를 제외할 수 있습니다. 우리의 테스트는 Choices가 없는 Question을 생성한 다음, 게시되지 않는지 테스트하고, 유사한 Questionwith Choices로 생성하고, 게시되는지 테스트합니다.

아마도 로그인한 관리자 사용자는 게시되지 않은 Questions를 볼 수 있지만, 일반 방문자는 볼 수 없어야 합니다. 다시 말하지만, 이를 달성하기 위해 소프트웨어에 추가해야 하는 모든 것은 테스트와 함께 제공되어야 합니다. 테스트를 먼저 작성한 다음, 코드가 테스트를 통과하도록 하거나, 먼저 코드에서 로직을 파악한 다음, 이를 증명하기 위해 테스트를 작성합니다.

특정 시점에서 당신은 당신의 테스트를 보고 당신의 코드가 테스트 과다로 고통받고 있는지 궁금해할 것입니다. 이는 우리를 다음으로 이끕니다.

테스트할 때, 많을수록 좋다

테스트가 통제 불능 상태로 커지고 있는 것처럼 보일 수 있습니다. 이 속도라면 곧 애플리케이션보다 테스트에 더 많은 코드가 있을 것이고, 나머지 코드의 우아한 간결함에 비해 반복은 미학적이지 않습니다.

상관없습니다. 테스트가 커지도록 놔두세요. 대부분의 경우, 테스트를 한 번 작성하고 잊어버릴 수 있습니다. 프로그램을 계속 개발함에 따라 유용한 기능을 계속 수행할 것입니다.

때로는 테스트를 업데이트해야 할 것입니다. Choices가 있는 Questions만 게시되도록 뷰를 수정한다고 가정해 봅시다. 이 경우, 기존 테스트 중 상당수가 실패할 것입니다. 어떤 테스트를 업데이트하여 최신 상태로 만들어야 하는지 정확히 알려주므로, 테스트는 그 자체를 관리하는 데 도움이 됩니다.

최악의 경우, 계속 개발하면서, 이제 중복된 테스트가 있다는 것을 알게 될 수 있습니다. 그것조차 문제가 되지 않습니다. 테스트에서 중복성은 좋은 것입니다.

테스트가 합리적으로 구성되어 있는 한, 관리 불가능해지지 않을 것입니다. 좋은 경험 법칙은 다음과 같습니다.

  • 각 모델 또는 뷰에 대해 별도의 TestClass
  • 테스트하려는 각 조건 집합에 대해 별도의 테스트 메서드
  • 기능을 설명하는 테스트 메서드 이름

추가 테스트

이 튜토리얼은 테스트의 기본 사항 중 일부만 소개합니다. 훨씬 더 많은 작업을 수행할 수 있으며, 매우 영리한 작업을 수행하기 위해 사용할 수 있는 매우 유용한 도구가 많이 있습니다.

예를 들어, 여기에서 우리의 테스트는 모델의 내부 로직과 뷰가 정보를 게시하는 방식을 다루었지만, Selenium과 같은 "브라우저 내" 프레임워크를 사용하여 HTML 이 실제로 브라우저에서 렌더링되는 방식을 테스트할 수 있습니다. 이러한 도구를 사용하면 Django 코드의 동작뿐만 아니라, 예를 들어 JavaScript 의 동작도 확인할 수 있습니다. 테스트가 브라우저를 시작하고, 마치 사람이 운전하는 것처럼 사이트와 상호 작용하는 것을 보는 것은 꽤 멋진 일입니다! Django 는 Selenium 과 같은 도구와의 통합을 용이하게 하기 위해 ~django.test.LiveServerTestCase를 포함합니다.

복잡한 애플리케이션이 있는 경우, 지속적 통합 (continuous integration)을 위해 모든 커밋마다 테스트를 자동으로 실행하여 품질 관리가 자체적으로 - 적어도 부분적으로 - 자동화되도록 할 수 있습니다.

애플리케이션의 테스트되지 않은 부분을 발견하는 좋은 방법은 코드 커버리지를 확인하는 것입니다. 이것은 또한 취약하거나 심지어 데드 코드 (dead code) 를 식별하는 데 도움이 됩니다. 코드 조각을 테스트할 수 없다면, 일반적으로 해당 코드를 리팩토링하거나 제거해야 함을 의미합니다. 커버리지는 데드 코드를 식별하는 데 도움이 됩니다. 자세한 내용은 topics-testing-code-coverage를 참조하십시오.

Django에서의 테스트 </topics/testing/index>는 테스트에 대한 포괄적인 정보를 제공합니다.

요약

축하합니다! 자동화된 테스트 생성 랩을 완료했습니다. LabEx 에서 더 많은 랩을 연습하여 기술을 향상시킬 수 있습니다.