개발/Python

[Python] TDD를 활용한 파이썬 unittest 사용 방법과 예제

growing-dev 2023. 2. 20. 22:39
반응형

오늘은 파이썬 unittest를 사용해서 내가 만든 프로그램을 테스트하는 방법에 대해서 알아보도록 하겠다.

 

 

파이썬 unittest 사용 방법과 간단한 예제

 

unit test란 무엇인가

unit test는 한국어로 단위 테스트라고 불리며 가능한 작은 단위인 함수나 클래스 단위를 각각 확인하는 테스트 방식을 뜻한다. 즉 어떤 큰 프로그램이 있더라도 결국 작은 단위의 함수가 클래스로 이루어져 있는데 그 작은 단위 하나하나를 테스트해서 궁극적으로 테스트의 커버리지를 확보하는 테스트 방식을 뜻한다. 개발자가 할 수 있는 간단하고 그나마 쉬운 테스트이다. 그래서 단위 테스트를 잘 활용해서 테스트 주도 개발(TDD)을 할 수 있다면 굉장히 좋은 코드가 나올 것이다. 물론 TDD가 완벽하거나 모든 것을 해결하는 것도 아니고 이걸 무조건 지켜야 한다는 것은 아니다. 하지만 테스트를 위주로 먼저 생각하고 개발을 시작한다는 역발상의 개발 방식은 꽤나 신선한 자극을 줄 수 있음에는 분명하다. 

 

파이썬 unit test

각 언어별로 unit test를 위한 프레임워크나 라이브러리 등이 존재한다. C나 C++에서는 ctest cpptest googletest 등과 같은 도구를 통해 unit test를 작성할 수 있다. 파이썬에서는 unit test와 pytest를 활용할 수 있으며 unit test는 파이썬 내장으로 지원하기 때문에 조금 더 쉽게 접근할 수 있고 pytest는 거기에 조금 더 기능을 추가한 것이라서 별도로 설치해야 한다. 나는 오늘 파이썬 내장으로 제공하는 unit test를 사용해서 간단한 예제를 만들어 보고 테스트 주도 개발(TDD)에 대해서 고민해 볼 예정이다.

 

 

파이썬 unit test 작성

우선 TDD로 개발하기 위해서는 테스트를 먼저 생각해야 한다. 이전에도 활용했던 factorial 함수를 생각해보았다. 

기본적으로 factorial 함수의 요구사항은 어떤 정수를 입력받으면 1부터 해당 정수까지의 곱을 취한 뒤 반환하는 함수이다. 일단 TDD로 개발한다고 하면 우선 테스트를 생각한다.

 

  • 1. 파이썬 unit test 를 import 하고 실행할 수 있는 환경을 만든다.
  • 2. 실제 구현할 함수를 형태만 구현하고 테스트를 만든다.
  • 3. 실제 함수를 구현한다.
  • 4. 테스트가 성공하는지 확인한다.
  • 5. 2~4를 반복한다.

 

 

 

1. 파이썬 unit test 를 import 하고 실행할 수 있는 환경을 만든다.

factorial_test.py 파일을 만들고 아래와 같이 unittest를 import 하고 FactorialTest class와 실행할 수 있도록 main으로 만든다. test case는 test_my_factorial로 정했고 우선 pass를 return 해주도록 틀을 만들었다.

 

factorial_test.py

import unittest


class FactorialTest(unittest.TestCase):

    def test_my_factorial(self):
        pass


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

 

 

2. 실제 구현할 함수를 형태만 구현하고 테스트를 만든다.

test_my_factorial 함수 내에 실제 활용될 법한 factorial 함수를 호출하도록 만들고 기대하는 결과를 비교하는 asserEqual을 통해 비교하도록 한다. 이때, 현재 factorial은 단순히 return 0 하는 것으로 만들었기 때문에 테스트는 실패할 수밖에 없다. 이것이 TDD의 핵심이다. 우선 실제 구현에 연연하지 말고 어떻게 사용할지, input은 뭐고 output은 무엇일지, 사용자 관점에서 어떻게 호출하는 게 좋을지를 고민해 보고 테스트부터 만드는 것이다.

 

factorial_test.py

import unittest
from factorial import factorial


class FactorialTest(unittest.TestCase):

    def test_my_factorial(self):
        self.assertEqual(1, factorial(1))


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

 

factorial.py

def factorial(n):
    return 0

 

실행 결과 일단 실패하는 TC를 만든다

 

3. 실제 함수를 구현한다.

아래와 같이 factorial 함수를 구현한다. 

 

factorial.py

def factorial(n):
    val = 1

    while n > 0:
        val *= n
        n -= 1

    return val

 

 

 

4. 테스트가 성공하는지 확인한다.

실행 결과 factorial(1)의 결과가 1과 같으므로 테스트가 성공하는 것을 확인할 수 있다.

코드 사진

 

실행 결과

 

 

5. 2~4를 반복한다.

새로운 함수가 추가되거나 기능이 추가될 때면 다시 2번으로 돌아가서 형태만 구현하고 테스트부터 만든다. 그리고 실패하는 것을 확인하고 실제 구현을 하고 테스트 성공 여부를 확인하면서 개발해 나간다. 또 테스트 케이스를 추가하면서 기존 구현의 예외 상황이 없는지 확인해볼 수 있다.

 

정상 테스트 케이스 추가하면서 예외 케이스 보완

만약 단순 1이 아니고 factorial(10)을 테스트 한다고 생각해 보자. 사실 일일이 구할 수도 있지만 그러기에는 너무 번거롭다. 내 생각엔 factorial의 경우 기존 math 모듈에서 제공하는 기능도 있기 때문에 그 둘의 결과가 같은 지 비교하는 것이 괜찮을 것이라고 생각했다.

import unittest
import math
from factorial import factorial


class FactorialTest(unittest.TestCase):

    def test_my_factorial(self):
        self.assertEqual(1, factorial(1))

    def test_my_factorial_10(self):
        self.assertEqual(math.factorial(10), factorial(10))


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

실행 결과

 

새로운 요구사항 혹은 예외 케이스로 인한 실패 발생

만약 새로운 요구사항으로 음의 정수를 입력받았을 때에도 처리할 수 있도록 요구사항 혹은 예외 상황이 발생했다고 가정하자. 요구 사항은 음의 정수를 받더라도 양의 정수로 계산하도록 처리할 수도 있고 그냥 예외 오류로 처리할 수도 있다. 내 경우는 양의 정수로 처리하도록 구현해 보았다. 즉 이 경우에도 요구 사항에 맞는 테스트 결과를 미리 생각하고 테스트를 구현한 다음 실패를 확인하고, 그다음 실제 구현을 하고 테스트가 성공하는지 지속적으로 확인하는 과정을 거친다.

 

class FactorialTest(unittest.TestCase):

    def test_my_factorial(self):
        self.assertEqual(1, factorial(1))

    def test_my_factorial_10(self):
        self.assertEqual(math.factorial(10), factorial(10))

    def test_my_factorial_minus10(self):
        self.assertEqual(math.factorial(10), factorial(-10))


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

 

실행 결과 실패한다

 

아래와 같이 음수로 들어왔을 때 양수로 변경하는 방어 코드를 추가했다. 그 결과 아래와 같이 3개의 TC 모두 성공하는 것을 확인할 수 있었다.

def factorial(n):
    if n < 0: #음수일 때 양수로 전환하는 방어 코드 추가
        n = -n
    val = 1

    while n > 0:
        val *= n
        n -= 1

    return val

 

수행 결과 성공

 

결론

여기까지 간단하게 파이썬에서 unittest를 활용하는 방법과 TDD로 개발하는 예제를 공부해보았다. 다음번엔 unittest나 pytest를 활용해서 조금 더 나은 unittest를 만드는 법에 대해서 공부해 볼 예정이다. 내가 생각한 방식이 정답은 아닐 것이고 틀린 부분도 있겠지만 이 글을 보는 사람이 TDD가 추구하는 개념은 어느 정도 익히고 이해할 수 있었으면 좋을 것 같다.

반응형