소프트웨어 개발에서 테스트는 코드의 품질을 보장하는 필수적인 과정입니다. Python에서는 표준 라이브러리에 포함된 unittest 프레임워크를 통해 효과적인 단위 테스트를 구현할 수 있습니다. 이 글에서는 unittest의 기본 개념부터 실전 활용법까지 상세히 알아보겠습니다.

unittest란 무엇인가?
unittest는 Python 표준 라이브러리에 포함된 단위 테스트 프레임워크로, Java의 JUnit에서 영감을 받아 설계되었습니다. 이 프레임워크는 테스트 자동화, 테스트 설정 및 종료 코드 공유, 테스트를 컬렉션으로 집계, 테스트와 보고 프레임워크의 독립성 등을 지원합니다.
unittest의 주요 개념
1. 테스트 케이스 (Test Case)
unittest의 기본 단위는 TestCase
클래스입니다. 이 클래스를 상속받아 테스트 메서드를 정의합니다. 각 테스트 메서드는 test_
로 시작해야 합니다.
2. 테스트 픽스처 (Test Fixture)
테스트 픽스처는 테스트 실행 전후에 필요한 준비와 정리 작업을 담당합니다. setUp()
과 tearDown()
메서드를 통해 구현합니다.
3. 테스트 스위트 (Test Suite)
여러 테스트 케이스를 그룹화하여 함께 실행할 수 있는 컬렉션입니다.
4. 테스트 러너 (Test Runner)
테스트를 실행하고 결과를 사용자에게 보여주는 컴포넌트입니다.
unittest 기본 사용법
간단한 테스트 케이스 작성하기
다음은 간단한 함수를 테스트하는 unittest 예제입니다:
# 테스트할 함수가 있는 모듈: my_function.py
def add(a, b):
return a + b
def multiply(a, b):
return a * b
이제 이 함수들을 테스트하는 코드를 작성해 보겠습니다:
# test3.py
import unittest
from my_function import add, multiply
class TestMyFunctions(unittest.TestCase):
def test_add(self):
self.assertEqual(add(3, 5), 8)
self.assertEqual(add(-1, 1), 0)
self.assertEqual(add(-1, -1), -2)
def test_multiply(self):
self.assertEqual(multiply(3, 5), 15)
self.assertEqual(multiply(-1, 1), -1)
self.assertEqual(multiply(-1, -1), 1)
if __name__ == '__main__':
unittest.main()
테스트 실행하기
테스트를 실행하는 방법은 두 가지가 있습니다:
- 파일에서 직접 실행:
python test3.py
- unittest 모듈 사용:
python -m unittest test3

테스트 픽스처 활용하기
테스트 전후에 특정 작업을 수행해야 할 때 테스트 픽스처를 활용합니다:
import unittest
import os
class TestFileOperations(unittest.TestCase):
def setUp(self):
# 테스트 전에 임시 파일 생성
self.test_file = 'test_file.txt'
with open(self.test_file, 'w') as f:
f.write('테스트 데이터')
def tearDown(self):
# 테스트 후 임시 파일 삭제
if os.path.exists(self.test_file):
os.remove(self.test_file)
def test_file_content(self):
with open(self.test_file, 'r') as f:
content = f.read()
self.assertEqual(content, '테스트 데이터')
def test_file_exists(self):
self.assertTrue(os.path.exists(self.test_file))
if __name__ == '__main__':
unittest.main()
주요 assertion 메서드
unittest는 다양한 assertion 메서드를 제공합니다:
assertEqual(a, b)
: a와 b가 같은지 확인assertNotEqual(a, b)
: a와 b가 다른지 확인assertTrue(x)
: x가 True인지 확인assertFalse(x)
: x가 False인지 확인assertIs(a, b)
: a가 b와 동일한 객체인지 확인assertIsNot(a, b)
: a가 b와 다른 객체인지 확인assertIsNone(x)
: x가 None인지 확인assertIsNotNone(x)
: x가 None이 아닌지 확인assertIn(a, b)
: a가 b에 포함되는지 확인assertNotIn(a, b)
: a가 b에 포함되지 않는지 확인assertRaises(exc, fun, *args, **kwds)
: 함수가 예외를 발생시키는지 확인
예외 테스트하기
함수가 특정 상황에서 예외를 발생시키는지 테스트하는 방법은 다음과 같습니다:
def divide(a, b):
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
class TestDivide(unittest.TestCase):
def test_divide_normal(self):
self.assertEqual(divide(10, 2), 5)
def test_divide_by_zero(self):
# 방법 1: with 구문 사용
with self.assertRaises(ValueError):
divide(10, 0)
# 방법 2: context manager 사용
context = self.assertRaises(ValueError)
with context:
divide(10, 0)
self.assertEqual(str(context.exception), "Cannot divide by zero")
테스트 스킵하기
특정 조건에서 테스트를 건너뛰어야 할 때 사용하는 데코레이터입니다:
import unittest
import sys
class TestSkipping(unittest.TestCase):
@unittest.skip("이 테스트는 항상 스킵됩니다")
def test_always_skipped(self):
self.fail("이 테스트는 실행되지 않아야 합니다")
@unittest.skipIf(sys.version_info < (3, 9), "Python 3.9 이상에서만 실행")
def test_python_version(self):
# Python 3.9 이상에서만 사용 가능한 기능 테스트
pass
@unittest.skipUnless(sys.platform.startswith("win"), "Windows에서만 실행")
def test_windows_only(self):
# Windows 전용 기능 테스트
pass
테스트 서브클래스
여러 테스트 클래스에서 공통 메서드를 재사용하고 싶을 때 상속을 활용할 수 있습니다:
class BaseTestCase(unittest.TestCase):
def setUp(self):
self.base_value = 10
def helper_method(self, value):
return self.base_value + value
class TestSpecificFeature(BaseTestCase):
def test_feature(self):
self.assertEqual(self.helper_method(5), 15)
테스트 스위트 구성하기
여러 테스트를 그룹화하여 실행하고 싶을 때 테스트 스위트를 사용합니다:
import unittest
# 테스트 케이스 클래스들
from test_module1 import TestClass1
from test_module2 import TestClass2, TestClass3
# 테스트 스위트 만들기
def create_test_suite():
test_suite = unittest.TestSuite()
# 개별 테스트 케이스 추가
test_suite.addTest(unittest.makeSuite(TestClass1))
test_suite.addTest(unittest.makeSuite(TestClass2))
# 특정 테스트 메서드만 추가
test_suite.addTest(TestClass3('test_specific_method'))
return test_suite
if __name__ == '__main__':
# 테스트 스위트 실행
runner = unittest.TextTestRunner()
test_suite = create_test_suite()
runner.run(test_suite)
unittest.mock 활용하기
외부 의존성이 있는 코드를 테스트할 때 mock 객체를 활용할 수 있습니다:
import unittest
from unittest.mock import Mock, patch
# 테스트할 함수
def get_user_data(user_id, api_client):
response = api_client.get_user(user_id)
if response.status_code == 200:
return response.data
return None
class TestUserData(unittest.TestCase):
def test_get_user_data_success(self):
# Mock API 클라이언트 생성
mock_client = Mock()
mock_response = Mock()
mock_response.status_code = 200
mock_response.data = {'name': 'John', 'age': 30}
mock_client.get_user.return_value = mock_response
# 함수 테스트
result = get_user_data(123, mock_client)
# 검증
self.assertEqual(result, {'name': 'John', 'age': 30})
mock_client.get_user.assert_called_once_with(123)
def test_get_user_data_failure(self):
# Mock API 클라이언트 생성
mock_client = Mock()
mock_response = Mock()
mock_response.status_code = 404
mock_client.get_user.return_value = mock_response
# 함수 테스트
result = get_user_data(999, mock_client)
# 검증
self.assertIsNone(result)
mock_client.get_user.assert_called_once_with(999)
@patch('my_module.api_client')
def test_with_patch(self, mock_api_client):
# patch 데코레이터로 모듈 내 객체를 mock으로 대체
mock_response = Mock()
mock_response.status_code = 200
mock_response.data = {'name': 'Jane', 'age': 25}
mock_api_client.get_user.return_value = mock_response
# 함수 호출 (api_client는 자동으로 mock으로 대체됨)
from my_module import get_user_data_with_global_client
result = get_user_data_with_global_client(456)
# 검증
self.assertEqual(result, {'name': 'Jane', 'age': 25})
테스트 커버리지 확인하기
코드의 어느 부분이 테스트되고 있는지 확인하려면 coverage
패키지를 활용할 수 있습니다:
# 설치 pip install coverage # 테스트 실행하며 커버리지 측정 coverage run -m unittest discover # 커버리지 리포트 생성 coverage report # HTML 형식의 상세 리포트 생성 coverage html
unittest 모범 사례
테스트 작성 시 고려사항
- 테스트 독립성 유지: 각 테스트는 다른 테스트에 의존하지 않고 독립적으로 실행될 수 있어야 합니다.
- 명확한 테스트 이름 사용: 테스트 메서드 이름은 무엇을 테스트하는지 명확히 나타내야 합니다.
- 하나의 테스트에서는 하나의 동작만 검증: 각 테스트는 단일 기능이나 동작을 검증해야 합니다.
- 테스트 코드도 유지보수 대상: 테스트 코드도 실제 코드처럼 깔끔하고 유지보수하기 쉽게 작성해야 합니다.
- 경계 조건 테스트: 정상 케이스뿐만 아니라 경계 조건과 예외 상황도 테스트해야 합니다.
결론
unittest는 Python에서 단위 테스트를 작성하기 위한 강력하고 유연한 프레임워크입니다. 기본적인 테스트 케이스 작성부터 복잡한 테스트 스위트 구성, mock 객체 활용까지 다양한 기능을 제공합니다. 효과적인 테스트 코드 작성은 소프트웨어의 품질을 높이고 버그를 조기에 발견하는 데 큰 도움이 됩니다.
unittest를 활용하여 테스트 주도 개발(TDD)을 실천하거나, 기존 코드의 리팩토링 시 안전망을 구축하는 등 다양한 방식으로 개발 프로세스를 개선해 보세요. 테스트는 단순한 검증 도구를 넘어 더 나은 설계와 구현을 이끌어내는 강력한 도구가 될 수 있습니다.
답글 남기기