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