11. Django에서 Test 진행하기

bruce1115·2023년 12월 25일
0

EduMax

목록 보기
12/12

Test의 필요성

학교에서, 그리고 많이들 프로젝트를 할 때 간과하는 부분이 바로 테스트 코드의 작성인 것 같다. 전에 Node.js로 백엔드를 짤 때, 처음 하는 프로젝트여서 미숙하여 제 시간에 기능 구현을 하는 데 급급해 테스트 코드는 손도 대지 못했었다.
사실 TDD(Test Driven Development)라는 개발 방법론이 굉장히 유명한 것처럼, 테스트 코드가 가져다 주는 이점은 굉장하다. 자동화된 테스트 작업을 통해 내가 짠 코드에 대한 즉각적인 피드백이 가능하고, 어디서 문제가 발생하였는지 명확하게 알 수 있다. 따라서 코드의 버그도 줄어들고, 불필요한 코드는 사전에 가지치기할 수 있을 것이다. 만약 TDD로 개발을 한다면 테스트 코드를 통해 해결해야 할 문제를 명확하게 정의할 수 있으므로 정확히 요구사항에 집중하는 코드를 짤 수 있을 것이다. 물론 개발 시간이 늘어나서 생산성이 떨어지지 않을까에 대한 우려도 있지만, 나중에 디버깅 과정에서 헤매는 것 역시 시간이 많이 드는 일이므로 TDD를 적용하면서 개발할 수 있도록 하자는 결정을 하였다.

Django에서 Test??

Django에는 다행히도 Test를 쉽게 하기 위한 도구들을 제공하고 있다. 전에 시험 삼아 만들어 본 Model인 Post를 테스트하는 코드는 다음과 같이 작성할 수 있다. 먼저 아래는 이전에 만들었던 Post의 코드이다.

class Post(models.Model):
    class Categories(models.TextChoices):
        FREE = "FR"
        NOTICE = "NO"
        KOREAN_QUESTION = "KQ"
        ENG_QUESTION = "EQ"
        MATH_QUESTION = "MQ"
        KOREAN_DATA = "KD"
        ENG_DATA = "ED"
        MATH_DATA = "MD"

    title = models.CharField(max_length=30)
    content = models.TextField()
    created_at = models.DateTimeField()
    category = models.CharField(
        max_length=2, choices=Categories.choices, default=Categories.FREE
    )
    author = models.ForeignKey(User, on_delete=models.CASCADE, related_name="posts")

여기서 드는 의문은 과연 어떤 부분을 테스트해야 할까이다. 기본적으로 Django에서 지원하는 부분은 굳이 중복하여 실행할 필요는 없다. 하지만 max_length와 같이 내가 임의로 지정한 field에 대해서는 반드시 테스트를 진행해야 한다. 추후에 바뀔 여지가 얼마든지 있기도 하고, 내가 짠 비즈니스 로직이기 때문이다. 간단한 예시로, 여기서 title의 max_length를 테스트하는 코드를 짜 보자.

from django.test import TestCase, TransactionTestCase

from .models import Post
from account.models import User

from django.utils import timezone


class PostModelTests(TransactionTestCase):
    def setUp(self):
        User.objects.create(
            username="TestUser1", nickname="T1", email="testtest1@gmail.com"
        )


    def test_Post_title_length_less_than_30(self):
        user1 = User.objects.get(id=1)

        Post.objects.create(
            title="Test1Test1Test1Test1Test1Test1",
            content="testtest2",
            created_at=timezone.now(),
            author=user1,
        )
        p = Post.objects.get(title="Test1Test1Test1Test1Test1Test1")
        self.assertEqual(p.content, "testtest2")

        with self.assertRaises(Exception):
            Post.objects.create(
                title="Test1Test1Test1Test1Test1Test1!!!!",  # over length 30
                content="testtest3",
                created_at=timezone.now(),
                author=user1,
            )

setUp을 이용해 초기 데이터를 생성하고, test_로 시작하는 각 테스트들을 작성한 후 assert로 시작하는 method를 이용하여 조건들을 비교해 주면 된다. 이 코드에서는 title이 30자인 경우 정상적으로 데이터가 만들어져야 하지만 30자를 초과하는 순간 DB에서 에러가 발생해야 하기 때문에, 이를 assertEqual, assertRaises를 통해 구현하였다.

일반적으로 Django에서 Test를 위해 제공하는 도구는 TestCase이다. 하지만 여기서는 TransactionTestCase를 상속받고 있는데, 이는 Django Test의 동작 방식 때문에 생기는 문제를 방지하기 위해서이다. 기본적으로 Django에서는 성능을 이유로 Test를 transaction 안에서 돌리는데, 테스트 코드에서 에러가 발생하고 이것이 transaction, 즉 atomic block 내에서 문제를 일으킨다. 따라서 DB 접근 시 TransactionManagementError가 발생하게 되어 제대로 된 테스트가 불가능해진다.
따라서 이를 해결하기 위해서는 TestCase가 아닌, transaction을 사용하지 않는 TransactionTestCase를 사용하면 된다.

참고한 글

Transaction Error 관련 Stackoverflow 글
MDN Django Test 관련 문서

profile
백엔드 개발자 꿈나무

0개의 댓글