Overview

반복적인 테스트에 지쳐가고 있던 무렵, TDD방법론을 접하게 되었습니다. TDD(Test Driven Development)는 테스트 주도적인 개발로 소스코드 작업 전에 테스트 코드를 먼저 작성해 소스수정에 대한 부담을 덜고 디버깅 시간을 줄일 수 있습니다.

TDD 장점
소스코드의 품질이 높다.
재설계 및 디버깅 시간이 절감된다.

TDD 단점
단기적 코드일 경우 생산성이 떨어진다.
실제 코드보다 테스트 케이스가 더 커질 수 있다.

파이썬에서 TDD가 필요한 이유
1) 파이썬에는 정적 타입 검사 기능이 없다. (Python 3.6 에서는 정적 타입 선언 가능)
2) 동적언어이기 때문에 TDD를 하기에 적합하다.
3) 파이썬은 간결성과 단순함으로 생산성이 높은 반면 런타임 오류가 발생할 수도 있다.
4) 파이썬을 신뢰할 수 있는 유일한 방법은 테스트를 하는 것이다.


파이썬 테스트 모듈 unittest

이번 글에서는 unittest를 사용해 단위 테스트를 해보겠습니다. unittest는 이미 내장되어 있어 따로 설치하지 않아도 되는 표준 라이브러리입니다.

사용방법
1) import unittest
2) unittest.TestCase 상속받는 하위 클래스 생성
3) TestCase.assert 메소드를 사용하여 테스트 코드를 간략화
4) unittest.main() 실행

그럼 간단한 예제로 단위 테스트를 해보겠습니다.

1.사칙연산 함수를 추가합니다.

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

def substract(a, b):
    return a - b

def division(a, b):
    return a / b

def multiply(a, b):
    return a * b



2. unittest.TestCase 상속받아 테스트 클래스를 생성합니다. 아래는 각각의 함수 결과값을 비교해 텍스트를 출력하는 코드입니다.

import unittest

class TddTest(unittest.TestCase):

    def testAdd(self):
        result = lib_calc.add(10, 20)
        if result == 30:
            print('testAdd OK')

    def testSubstract(self):
        result = lib_calc.substract(20, 30)

        if result > 0:
            boolval = True
        else:
            boolval = False

        if boolval == False:
            print('testSubstract Error')

    def testDivision(self):
        try:
            lib_calc.division(4, 0)
        except Exception as e:
            print(e)

    def testMultiply(self):
        result = lib_calc.multiply(10, 9)

        if result < 100:
            print('testMultiply Error')

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



3.결과: 해당 조건에 만족해 작성한 텍스트가 출력됩니다.

01



이번에는 unittest에서 지원하는 TestCase.assert 메소드를 사용해 간략하게 소스를 수정해보겠습니다.

TestCase.assert 메소드
1) assertEqual(A, B, Msg) - A, B가 같은지 테스트
2) assertNotEqual(A, B, Msg) - A, B가 다른지 테스트
3) assertTrue(A, Msg) - A가 True인지 테스트
4) assertFalse(A, Msg) - A가 False인지 테스트
5) assertIs(A, B, Msg) - A, B가 동일한 객체인지 테스트
6) assertIsNot(A, B, Msg) - A, B가 동일하지 않는 객체인지 테스트
7) assertIsNone(A, Msg) - A가 None인지 테스트
8) assertIsNotNone(A, Msg) - A가 Not None인지 테스트
9) assertRaises(ZeroDivisionError, myCalc.add, 4, 0) - 특정 에러 확인

1. TestCase.assert 메소드 사용
TestCase.assert 메소드를 사용하여 에러를 발생시켜 보겠습니다.

import unittest

class TddTest(unittest.TestCase):

    def testAdd(self):
        result = lib_calc.add(10, 20)

        # 결과 값이 일치 여부 확인
        self.assertEqual(result, 31)

    def testSubstract(self):
        result = lib_calc.substract(20, 10)

        if result > 10:
            boolval = True
        else:
            boolval = False

        # 결과 값이 True 여부 확인
        self.assertTrue(boolval)

    def testDivision(self):
        # 결과 값이 ZeroDivisionError 예외 발생 여부 확인
        self.assertRaises(ZeroDivisionError, lib_calc.division, 4, 1)

    def testMultiply(self):
        nonechk = True

        result = lib_calc.multiply(10, 9)

        if result > 100:
            nonechk = None

        # 결과 값이 None 여부 확인
        self.assertIsNone(nonechk)

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



2. 결과
1) 테스트가 실패해도 다른 테스트에 영향을 미치지 않음
2) 실패한 위치와 이유를 알 수 있음

02



다음으로 setUp(), tearDown() 메소드를 사용하여 반복적인 테스트 메소드 실행 전, 실행 후의 동작을 처리해보겠습니다.

TestCase 메소드
1) setUp() - TestCase클래스의 매 테스트 메소드가 실행 전 동작
2) tearDown() - 매 테스트 메소드가 실행 후 동작

1. setUp(), tearDown() 메소드 사용
- setUp() 메소드로 전역 변수에 값을 지정
- tearDown() 메소드로 “ 결과 값 : ” 텍스트 출력

import unittest

class TddTest(unittest.TestCase):

    aa = 0
    bb = 0
    result = 0

    # 매 테스트 메소드 실행 전 동작
    def setUp(self):
        self.aa = 10
        self.bb = 20

    def testAdd(self):
        self.result = lib_calc.add(self.aa, self.bb)

        # 결과 값이 일치 여부 확인
        self.assertEqual(self.result, 31)

    def testSubstract(self):
        self.result = lib_calc.substract(self.aa, self.bb)

        if self.result > 10:
            boolval = True
        else:
            boolval = False

        # 결과 값이 True 여부 확인
        self.assertTrue(boolval)

    def testDivision(self):
        # 결과 값이 ZeroDivisionError 예외 발생 여부 확인
        self.assertRaises(ZeroDivisionError, lib_calc.division, 4, 1)

    def testMultiply(self):
        nonechk = True

        self.result = lib_calc.multiply(10, 9)

        if self.result > 100:
            nonechk = None

        # 결과 값이 None 여부 확인
        self.assertIsNone(nonechk)

    # 매 테스트 메소드 실행 후 동작
    def tearDown(self):
        print(' 결과 값 : ' + str(self.result))

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



2. 결과
- setUp() 메소드로 지정한 값으로 테스트를 수행
- tearDown() 메소드로 각각의 테스트 메소드 마다 “ 결과 값 : ” 텍스트 출력

03



실행 명령어 여러 옵션을 사용하여 실행 결과를 출력해보겠습니다.

실행 명령어
python -m unittest discover [option]
1. -v : 상세 결과
2. -f : 첫 번째 실패 또는 오류시 중단
3. -s : 시작할 디렉토리
4. -p : 테스트 파일과 일치하는 패턴
5. -t : 프로젝트의 최상위 디렉토리


1. 상세 결과
테스트 메소드명 및 해당 클래스명 출력

04



2. 첫 번째 실패 또는 오류시 중단
첫 번째 테스트에서 오류 발생하여 중단

05



3. 여러 옵션 실행
현재경로 디렉토리 안에 tdd_test*.py 패턴에 속하는 모든 파일의 상세 결과

06




Conclusion

지금까지 파이썬에서 unittest 모듈을 이용한 테스트 코드를 작성했습니다. 처음에는 귀찮고 번거롭지만 테스트 코드를 먼저 작성하는 습관을 길러보세요. 분명 높은 품질의 소스코드를 만들 수 있을 겁니다!


참고
Python 테스트 시작하기
파이썬 TDD 101


곽정섭 과장 | R&D 개발1팀
kwakjs@brandi.co.kr
브랜디, 오직 예쁜 옷만