[엉박사] 1.10.4 Unit Test

impala·2023년 1월 12일
0
post-thumbnail

1.10.4 Unit Test

프로젝트 막바지로 갈수록 여러 기능들이 추가되면서 예상하지 못한 상황들이 발생하게 되었다. 그중에서는 오류나 예외를 통해 문제를 식별할 수 있는 경우도 있었지만 오류가 발생하지는 않지만 논리적으로 잘못된 동작을 하는 경우도 있었다. 이런 경우에는 문제가 있는 코드 뿐만 아니라 이로인해 영향을 받는 코드까지 수정을 해야 했기 때문에 일반적인 경우보다 디버깅을 하는데 더 많은 시간이 들었다.

앞서 언급했듯이 이전까지는 프로젝트의 수준이 그렇게까지 높지 않아 테스트코드의 중요성을 느끼지 못하였다. 하지만 프로젝트의 규모가 커질수록 새로 만든 기능이 기존 코드에 영향을 주는 경우가 많아지고, 이로인해 예상하지 못한 부분에서 문제가 생길 가능성이 높아지기 때문에 테스트코드를 작성하여 항상 코드의 무결성을 보장할 수 있게끔 개발을 하는것이 좋다. 따라서 아래에 파이썬과 장고에서 Unit Test를 하는 방법에 대해 정리해두었다.

Python Unit Test

Unit Test는 프로그램이 개발자가 의도하는대로 작동하는지 검증하는 절차로 하나의 유닛(메소드)단위로 유효성을 검증하는 것을 의미한다. Unit Test의 상위 단계로는 여러 클래스간의 상호작용을 검사하는 Integration Test와 실제 서비스의 기능이 정상적으로 동작하는지를 검사하는 UI Test가 있다.

파이썬에서는 unittest모듈을 사용하여 Unit Test를 진행한다. unittest모듈을 사용하여 테스트 코드를 작성하는 전반적인 과정은 아래와 같다.

  1. unittest모듈을 import한다
  2. TestClass를 만들고 unittest.Testclass를 상속받는다
  3. @classmethod decorator와 함께 setUpClass(cls)와 tearDownClass(cls)를 구현한다
    • setUpClass(cls) : 테스트 시작 전 Class생성시 호출되는 메소드로 테스트 환경을 초기화한다. cls인자를 통해 클래스변수를 새로 만들거나 접근할 수 있다.
    • tearDownClass(cls) : 테스트 이후 Class소멸시 호출되는 메소드로 테스트 환경을 정리한다. cls인자를 통해 클래스변수를 새로 만들거나 접근할 수 있다.
  4. 각 테스트 케이스의 실행 전후로 호출되는 setUpClass(self)와 tearDownClass(self)를 구현한다
  5. test_로 시작하는 테스트코드를 구현한다
  6. unittest.main()을 호출하여 테스트코드를 실행한다.
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)

Django Unit Test

장고에서는 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를 상속받은 것이기 때문에 몇 가지 변경사항을 제외하고는 위에서 소개한 방법과 동일하게 테스트코드를 작성하면 된다.

  • @classmethod setUpClass(cls)가 @classmethod setUpTestData(cls)로 변경
  • @classmethod tearDownClass(cls)가 @classmethod tearDownClass(cls)로 변경
  • 서버에 요청을 보내기 위해서 self.client를 통해 HTTP메소드를 사용함
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 Unit 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')

0개의 댓글