프로젝트 막바지로 갈수록 여러 기능들이 추가되면서 예상하지 못한 상황들이 발생하게 되었다. 그중에서는 오류나 예외를 통해 문제를 식별할 수 있는 경우도 있었지만 오류가 발생하지는 않지만 논리적으로 잘못된 동작을 하는 경우도 있었다. 이런 경우에는 문제가 있는 코드 뿐만 아니라 이로인해 영향을 받는 코드까지 수정을 해야 했기 때문에 일반적인 경우보다 디버깅을 하는데 더 많은 시간이 들었다.
앞서 언급했듯이 이전까지는 프로젝트의 수준이 그렇게까지 높지 않아 테스트코드의 중요성을 느끼지 못하였다. 하지만 프로젝트의 규모가 커질수록 새로 만든 기능이 기존 코드에 영향을 주는 경우가 많아지고, 이로인해 예상하지 못한 부분에서 문제가 생길 가능성이 높아지기 때문에 테스트코드를 작성하여 항상 코드의 무결성을 보장할 수 있게끔 개발을 하는것이 좋다. 따라서 아래에 파이썬과 장고에서 Unit Test를 하는 방법에 대해 정리해두었다.
Unit Test는 프로그램이 개발자가 의도하는대로 작동하는지 검증하는 절차로 하나의 유닛(메소드)단위로 유효성을 검증하는 것을 의미한다. Unit Test의 상위 단계로는 여러 클래스간의 상호작용을 검사하는 Integration Test와 실제 서비스의 기능이 정상적으로 동작하는지를 검사하는 UI Test가 있다.
파이썬에서는 unittest모듈을 사용하여 Unit Test를 진행한다. unittest모듈을 사용하여 테스트 코드를 작성하는 전반적인 과정은 아래와 같다.
import unittest
class SampleTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
# 클래스가 생성될 때 수행할 작업
@classmethod
def tearDownClass(cls):
# 클래스가 소멸될 때 수행할 작업
def setUp(self):
# 각 테스트케이스가 실행되기 전에 수행할 작업
def tearDown(self):
# 각 테스트케이스가 실행된 후 수행할 작업
def test_runs_1(self):
# 하나의 유닛에 대한 테스트케이스
if __name__ == '__main__':
unittest.main() # 전체 테스트 코드 실행
unittest모듈에서 제공하는 테스트케이스에 사용되는 메소드들은 다음과 같다.
Method | 통과 조건 |
---|---|
assertEqual(a,b) | a == b |
assertNotEqual(a,b) | a != b |
assertTrue(x) | bool(x) is True |
assertFalse(x) | bool(x) is False |
assertIs(a,b) | a is b |
assertIsNot(a,b) | a is not b |
assertIsNone(x) | x is None |
assertIsNotNone(x) | x is not None |
assertIn(a,b) | a in b |
assertNotIn(a,b) | a not in b |
assertIsInstance(a,b) | isinstance(a,b) |
assertNotIsInstance(a,b) | not isinstance(a,b) |
장고에서는 unittest.TestCase를 상속받은 django.test.TestCase를 제공하여 Unit Test를 지원한다.
장고 프로젝트에서 앱 생성시 기본적으로 test.py라는 파일이 생성되는데, 이곳에 테스트 코드를 작성하거나 test*.py라는 이름의 파일을 만들어 테스트 코드를 작성하면 장고에서 자동으로 테스토코드로 인식하여 테스트를 진행한다.
└─app/
...
/tests/
└─__init__.py (empty)
test_models.py
test_views.py
test_forms.py
...
장고의 TestCase는 unittest의 TestCase를 상속받은 것이기 때문에 몇 가지 변경사항을 제외하고는 위에서 소개한 방법과 동일하게 테스트코드를 작성하면 된다.
from django.test import TestCase # 장고에서 제공하는 TestCase를 import
from django.urls import reverse
class QuestionModelTests(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)
def test_no_questions(self):
# 서버로의 요청에 대한 테스트 : client 인스턴스를 활용하여 서버로 요청을 보냄
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'], [])
또한 테스트를 실행시키려면 다음 명령을 실행한다
python manage.py test
DRF에서도 같은 방식으로 django.test.TestCase를 상속받은 rest_framework.APITestCase를 통해 Unit Test를 지원하고, 방법 또한 TestCase대신 APITestCase를 상속받는 것만을 제외하면 장고와 같다
from django.urls import reverse
from rest_framework import status
from rest_framework.test import APITestCase # DRF에서 제공하는 APITestCase를 import
from myproject.apps.core.models import Account
class AccountTests(APITestCase):
# client 인스턴스를 통해 서버로 요청을 전송
def test_create_account(self):
url = reverse('account-list')
data = {'name': 'DabApps'}
response = self.client.post(url, data, format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(Account.objects.count(), 1)
self.assertEqual(Account.objects.get().name, 'DabApps')