[SWE-004][기초] 실패하는 테스트부터 시작하는 TDD와 테스트 더블 > IT 기술 공유

본문 바로가기
사이트 내 전체검색

IT 기술 공유

[SWE-004][기초] 실패하는 테스트부터 시작하는 TDD와 테스트 더블

페이지 정보

profile_image
작성자 기술팀장
댓글 0건 조회 250회 작성일 26-08-31 16:35

본문

[이번 수업]
TDD(Test-Driven Development, 테스트 주도 개발)는 구현보다 기대 행동을 테스트로 먼저 표현하는 방식입니다. 실패를 확인하고, 통과할 최소 코드를 쓰고, 통과 상태에서 구조를 정리합니다.

[선수지식]
Python 함수·클래스·예외의 기초와 터미널에서 Python 파일을 실행하는 방법이 필요합니다.

[학습목표]
1. 실패→통과→정리의 짧은 TDD 반복을 설명한다.
2. 결과값과 예외를 unittest로 검증한다.
3. 실제 의존성을 Mock으로 교체하고 호출 여부를 확인한다.

[핵심개념]
단위 테스트는 작은 코드 단위의 관찰 가능한 행동을 확인합니다. TDD에서는 ‘가격 1,200원과 800원을 결제하면 합계 2,000원을 반환하고 알림을 한 번 보낸다’처럼 구체적인 예를 먼저 테스트로 씁니다. 원하는 이유로 실패하는지 본 뒤 통과할 코드만 작성하고, 통과하면 이름과 중복을 정리합니다.

테스트 더블은 테스트하기 어려운 협력 객체를 대신합니다. Stub은 정해진 값을 돌려주고, Fake는 단순한 동작 구현이며, Mock은 호출을 기록해 검증합니다. 의존성 주입은 필요한 객체를 함수 안에서 만들지 않고 매개변수로 받는 방식입니다. 운영에서는 실제 알림 객체, 테스트에서는 Mock을 전달할 수 있습니다.

`create_autospec(Notifier, instance=True)`는 Notifier의 메서드 모양을 따르는 Mock을 만듭니다. `assert_called_once_with`는 정확한 메시지로 한 번 호출됐는지, `assert_not_called`는 호출되지 않았는지 확인합니다.

[따라하기]
아래 내용을 test_checkout.py로 저장하세요.

```python
import unittest
from unittest.mock import create_autospec

class Notifier:
    def send(self, message):
        raise NotImplementedError

def checkout(prices, notifier):
    if any(price < 0 for price in prices):
        raise ValueError("price must be zero or greater")
    total = sum(prices)
    notifier.send(f"결제 금액: {total}원")
    return total

class CheckoutTest(unittest.TestCase):
    def test_returns_total_and_sends_message(self):
        notifier = create_autospec(Notifier, instance=True)
        result = checkout([1200, 800], notifier)
        self.assertEqual(result, 2000)
        notifier.send.assert_called_once_with("결제 금액: 2000원")

    def test_rejects_negative_price(self):
        notifier = create_autospec(Notifier, instance=True)
        with self.assertRaises(ValueError):
            checkout([1000, -1], notifier)
        notifier.send.assert_not_called()

if __name__ == "__main__":
    unittest.main(verbosity=2)
```

Windows PowerShell에서는 `py -m unittest -v test_checkout.py`, macOS·Linux에서는 `python3 -m unittest -v test_checkout.py`를 실행합니다. 두 테스트 이름 뒤에 `ok`, 마지막에 아래 결과가 보이면 통과입니다.

```text
Ran 2 tests in ...
OK
```

처음에는 checkout 본문을 `raise NotImplementedError`로 두고 실행하세요. 첫 테스트의 ERROR를 확인한 뒤 위 구현으로 바꾸어 통과시키고, 검증을 유지한 채 이름과 중복을 정리합니다.

[흔한 실수]
구현부터 완성하면 테스트 자체의 오류를 놓치기 쉽습니다. 내부 호출을 모두 Mock으로 검사하면 구조 변경에도 테스트가 깨집니다. 합계·예외·외부 알림처럼 관찰할 행동과 중요한 경계만 확인하세요.

[보안 주의]
단위 테스트에서 실제 결제·메일·문자 API를 호출하지 마세요. 운영 키, 실제 수신자, 개인정보를 코드나 출력에 넣지 말고 로컬 가짜 값과 Mock을 사용하세요. 외부 통합은 본인 소유의 격리된 테스트 환경에서 검증합니다.

[직접 해볼 과제]
1. 빈 가격 목록은 합계 0과 ‘결제 금액: 0원’을 만드는 테스트를 먼저 추가하세요.
2. 가격이 문자열이면 TypeError가 발생하고 알림은 호출되지 않는 테스트를 작성하세요.
3. Mock 대신 messages 목록을 가진 FakeNotifier를 직접 만들어 같은 행동을 검증하세요.

[확인문제]
1. TDD에서 구현 전에 실패하는 테스트를 실행하는 이유는 무엇인가요?
2. 이번 Mock은 실제 Notifier 대신 어떤 두 가지를 확인하나요?
3. 내부 구현을 지나치게 Mock으로 검사하면 리팩터링 때 어떤 문제가 생기나요?

[다음 학습]
SWE-005에서는 코드를 읽고 개선점을 전달하는 코드 리뷰를 배웁니다.

[공식 참고 자료]
Python unittest 공식 문서: https://docs.python.org/3/library/unittest.html
Python unittest.mock 공식 문서: https://docs.python.org/3/library/unittest.mock.html
Python Mock 활용 예제: https://docs.python.org/3/library/unittest.mock-examples.html

댓글목록

등록된 댓글이 없습니다.

회원로그인

회원가입

사이트 정보

회사명 : 회사명 / 대표 : 대표자명
주소 : OO도 OO시 OO구 OO동 123-45
사업자 등록번호 : 123-45-67890
전화 : 02-123-4567 팩스 : 02-123-4568
통신판매업신고번호 : 제 OO구 - 123호
개인정보관리책임자 : 정보책임자명

접속자집계

오늘
1,791
어제
5,103
최대
16,772
전체
772,845
Copyright © 소유하신 도메인. All rights reserved.