unittest 기초: 표준 라이브러리로 시작하는 테스트
파이썬에 내장된 unittest로 TestCase를 작성하는 법, assert 메서드와 setUp·tearDown 생명주기, 테스트를 실행하고 읽는 법까지 완전 정복합니다.
지난 글에서 의존성을 lock으로 고정해 재현 가능한 빌드를 만드는 법을 다뤘다. 환경이 안정됐다면 이제 코드가 실제로 의도대로 동작하는지 자동으로 확인할 차례다. 테스트는 “내가 짠 코드가 지금도, 그리고 내일 고친 뒤에도 맞게 동작한다”는 사실을 사람이 일일이 손으로 확인하지 않아도 되게 해 준다. 그 출발점으로 가장 좋은 것이 파이썬에 처음부터 들어 있는 unittest다. 외부 패키지를 설치하지 않아도, 표준 라이브러리만으로 곧바로 테스트를 짤 수 있다.
TestCase가 테스트의 단위다
unittest의 세계에서 테스트는 클래스 안에 모인다. unittest.TestCase를 상속한 클래스를 만들고, 그 안에 test_로 시작하는 메서드를 정의하면 각 메서드가 하나의 독립된 테스트가 된다. 이름 규칙이 핵심이다 — 메서드 이름이 test_로 시작해야만 테스트 러너가 그것을 테스트로 인식한다.
import unittest
def add(a, b):
return a + b
class TestMath(unittest.TestCase):
def test_add_positive(self):
self.assertEqual(add(1, 1), 2)
def test_add_negative(self):
self.assertEqual(add(-1, -1), -2)
if __name__ == "__main__":
unittest.main()
이 파일을 python test_math.py로 직접 실행하거나, 더 흔하게는 python -m unittest로 실행한다. 러너는 TestMath 안에서 test_로 시작하는 메서드를 모두 찾아 각각을 따로 실행한다. 한 메서드가 실패해도 다른 메서드는 멈추지 않고 계속 돌아간다.
assert 메서드로 기대를 표현한다
unittest는 단순한 assert 문 대신 전용 검증 메서드를 제공한다. 이 메서드들은 실패했을 때 “무엇이 기대됐고 실제로 무엇이 나왔는지”를 친절하게 출력해 준다. 가장 자주 쓰는 것들만 봐도 충분하다.
class TestAssertions(unittest.TestCase):
def test_examples(self):
self.assertEqual(2 + 2, 4) # ==
self.assertNotEqual(2 + 2, 5) # !=
self.assertTrue(3 > 1) # 참인지
self.assertFalse(1 > 3) # 거짓인지
self.assertIn(2, [1, 2, 3]) # 멤버십
self.assertIsNone(None) # None 인지
def test_exception(self):
with self.assertRaises(ZeroDivisionError):
1 / 0
assertEqual(a, b)가 실패하면 단순히 “False”가 아니라 2 != 5 같은 비교 정보를 보여 준다. 예외가 발생해야 하는 상황은 assertRaises를 with 블록과 함께 써서, 블록 안의 코드가 해당 예외를 던지는지 검증한다.
setUp과 tearDown: 준비와 뒷정리
여러 테스트가 같은 준비물을 필요로 할 때가 많다. 임시 파일, 데이터베이스 연결, 초기화된 객체 같은 것이다. 매 테스트 메서드마다 똑같은 준비 코드를 복사하는 대신, setUp 메서드에 한 번 적어 두면 된다. setUp은 각 테스트 메서드가 실행되기 직전에 자동으로 호출되고, tearDown은 각 테스트가 끝난 직후에 호출된다.
class TestAccount(unittest.TestCase):
def setUp(self):
# 테스트마다 새 계좌를 만든다
self.account = Account(balance=100)
def test_deposit(self):
self.account.deposit(50)
self.assertEqual(self.account.balance, 150)
def test_withdraw(self):
self.account.withdraw(30)
self.assertEqual(self.account.balance, 70)
중요한 점은 setUp이 테스트 클래스당 한 번이 아니라 테스트 메서드마다 매번 실행된다는 것이다. 그래서 test_deposit에서 잔액을 바꿔도 test_withdraw는 다시 100으로 초기화된 깨끗한 계좌에서 시작한다. 이렇게 각 테스트가 서로의 상태에 영향을 주지 않는 격리(isolation) 가 좋은 테스트의 핵심 원칙이다. 테스트 사이에 순서 의존성이 생기면, 어느 날 실행 순서가 바뀌었을 때 이유 없이 깨지는 테스트가 된다.
실행 결과를 읽는 법
python -m unittest -v로 실행하면 각 테스트의 이름과 결과가 한 줄씩 나온다. 점(.)은 통과, F는 실패(assert 불일치), E는 에러(예상치 못한 예외)를 뜻한다. 실패하면 어느 테스트의 몇 번째 줄에서, 무엇을 기대했는데 무엇이 나왔는지가 함께 출력된다.
test_add_negative (test_math.TestMath) ... ok
test_add_positive (test_math.TestMath) ... ok
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
이 출력의 마지막 줄 OK 하나를 보기 위해 테스트를 짠다고 해도 과언이 아니다. 리팩터링을 하거나 기능을 추가한 뒤 이 OK가 그대로 떠 있으면, 적어도 테스트가 검증하는 범위 안에서는 망가뜨린 게 없다는 뜻이다.
unittest는 표준 라이브러리라는 점, 그리고 클래스 기반의 명시적인 구조 덕분에 견고하다. 다만 클래스와 전용 assert 메서드를 매번 쓰는 것이 다소 장황하게 느껴질 수 있는데, 다음 글에서 살펴볼 pytest는 바로 그 장황함을 덜어 준다. 하지만 setUp·tearDown·격리 같은 핵심 개념은 도구가 바뀌어도 그대로 쓰이니, 여기서 익힌 사고방식이 토대가 된다.
지난 글: 버전 고정과 lock 파일: 재현 가능한 빌드
다음 글: pytest 기초: assert 한 줄로 쓰는 테스트
읽어주셔서 감사합니다. 😊