QA

테스트 주도 개발(Test-Driven Development, TDD) 이해하기

yoongrammer 2024. 11. 8. 08:43
728x90

테스트 주도 개발(TDD)은 코드 작성 전에 테스트를 먼저 작성해 기능을 하나씩 완성해가는 방법론입니다.
이번 글에서는 TDD의 기본 개념과 단계, 장단점을 살펴보고, Python 예제를 통해 TDD 방식으로 간단한 덧셈 함수를 구현해 보겠습니다.

TDD란 무엇인가?


테스트 주도 개발(Test-Driven Development, TDD)은 코드 작성에 앞서 테스트를 먼저 작성하는 개발 방법론입니다.
이 방식은 코드가 의도대로 작동하는지 빠르게 확인하고, 안정적인 코드베이스를 구축하는 데 효과적입니다.
보통 기능을 먼저 구현하고 나중에 테스트를 추가하는 것과 달리, TDD에서는 테스트 코드 작성 ➔ 기능 구현 ➔ 리팩터링이라는 단계를 반복하며 점진적으로 코드를 완성합니다.

TDD의 세 가지 단계


TDD는 Red, Green, Refactor 세 가지 단계로 이루어집니다.

  1. Red 단계: 실패하는 테스트를 먼저 작성합니다. 이 단계에서 테스트가 실패하면, 우리가 구현할 기능이 아직 없다는 의미입니다.
  2. Green 단계: 테스트를 통과할 수 있는 최소한의 코드를 작성하여 테스트를 성공시킵니다. 이때 중요한 것은 최소한으로 작성하는 것이며, 완벽하지 않아도 괜찮습니다.
  3. Refactor 단계: 코드를 리팩터링하여 중복을 제거하고 유지보수성을 높입니다. 리팩터링 후에도 모든 테스트가 통과해야 합니다.

TDD의 장점


  1. 코드 품질 향상
    • TDD는 코드가 테스트를 통과해야 하기 때문에 보다 견고한 코드가 작성됩니다. 기능의 명세서 역할도 해주어 코드의 의도를 명확히 이해할 수 있습니다.
  2. 디버깅 시간 절약
    • 테스트가 실패할 때마다 원인을 빠르게 파악할 수 있어, 디버깅 시간을 절약할 수 있습니다.
  3. 리팩터링이 쉬움
    • 변경 사항이 생겨도 테스트가 예상한 대로 작동하는지 확인할 수 있어, 코드 리팩터링이 수월해집니다.
  4. 빠른 피드백 루프
    • 코드가 의도한 대로 동작하는지 즉각적으로 피드백을 받을 수 있습니다.

TDD의 단점


  1. 초기 학습 곡선
    • 테스트를 먼저 작성하는 방식이 익숙하지 않을 수 있으며, 숙달되기까지 시간이 필요합니다.
  2. 개발 속도 저하
    • 테스트와 기능을 반복적으로 작성하므로 초기 개발 속도가 느려질 수 있습니다. 그러나 장기적으로 보면 코드 유지보수와 디버깅에 드는 시간을 줄여 결과적으로 효율을 높입니다.
  3. 모든 상황을 다 다루기 어려움
    • UI나 복잡한 비즈니스 로직의 경우, TDD로 모든 경우를 커버하는 데 한계가 있을 수 있습니다.

TDD 적용 예제 (Python)


TDD의 이해를 돕기 위해 Python으로 간단한 예제를 만들어 보겠습니다. 간단한 덧셈 함수를 구현하는 과정에서 TDD 방식을 적용해 보겠습니다.

1. 실패하는 테스트 작성 (Red 단계)

sum.py 파일에 덧셈 함수를 작성하지 않고, test_sum.py 파일에 테스트 코드를 먼저 작성합니다.

# test_sum.py

import unittest
from sum import add

class TestAddFunction(unittest.TestCase):
    def test_add_two_numbers(self):
        self.assertEqual(add(2, 3), 5)  # 2 + 3 = 5를 기대
        self.assertEqual(add(-1, 1), 0)  # -1 + 1 = 0을 기대

if __name__ == "__main__":
    unittest.main()

이 상태에서 테스트를 실행하면, add 함수가 정의되지 않아 오류가 발생합니다. (Red 단계)

728x90

2. 최소한의 코드 작성 (Green 단계)

테스트를 통과시키기 위해 sum.py 파일에 add 함수를 추가합니다.

# sum.py

def add(a, b):
    return a + b

이제 테스트를 다시 실행하면 성공하게 됩니다. 여기서 중요한 것은, 최소한의 코드로 테스트를 통과시키는 것입니다. (Green 단계)

3. 리팩터링 (Refactor 단계)

이 함수는 간단해서 추가 리팩터링이 필요하지 않지만, 중복 코드가 생기거나 기능이 복잡해질 경우 리팩터링을 통해 코드를 개선합니다.

이번에는 잘못된 입력값이 들어올 경우를 고려해 테스트 케이스를 추가해 보겠습니다. 예를 들어, 숫자가 아닌 문자열이 들어올 경우를 테스트해봅니다.

# test_sum.py

class TestAddFunction(unittest.TestCase):
    def test_add_two_numbers(self):
        self.assertEqual(add(2, 3), 5)
        self.assertEqual(add(-1, 1), 0)

    def test_add_invalid_input(self):
        with self.assertRaises(TypeError):  # 문자열을 넣으면 TypeError를 기대
            add("two", 3)

이제 이 테스트가 통과할 수 있도록 add 함수에 입력값 검증을 추가합니다.

# sum.py

def add(a, b):
    if not isinstance(a, (int, float)) or not isinstance(b, (int, float)):
        raise TypeError("Both arguments must be numbers")
    return a + b

이로써 모든 테스트가 성공하게 됩니다. 여기까지가 TDD의 마지막 단계인 리팩터링까지 완료한 것입니다.

마무리


TDD는 초반에 학습 곡선이 있지만 코드의 품질과 유지보수성을 높이는 강력한 방법입니다.

코드 작성 전 실패하는 테스트를 먼저 작성하고 이를 점진적으로 통과시키며 발전시켜 나가는 TDD의 반복적인 사이클을 통해, 코드를 보다 안정적이고 신뢰성 있게 만들 수 있습니다.

TDD는 단순히 테스트 도구가 아닌, 코드 품질과 개발 철학을 위한 중요한 과정이라고 할 수 있습니다.

728x90