programming코딩

Start Python #2 Unit Test 사용하기 ( feat. pytest )

저는 pytest 를 coding중에 Unit Test 를 위해서 사용합니다.

저의 Python 환경은 3.4이상이며 저는 현재 3.6.9를 사용합니다. 3.4이하에서는 에러가 발생할 수 있습니다.

반드시 pytest를 사용해야 하는 것은 아닙니다.

다른 unit test lib들이 많이 있습니다만 저는 pytest를 주로 사용하기 때문에 앞으로 pytest를 사용하면서 코드 설명해나갈 예정입니다.

오늘의 핵심은 if __name__ == ‘__main__’: 대신 pytest를 사용하는 것을 설명합니다.

pytest 설치

pip3 install pytest

pip install pytest

환경에 맞게 사용하시면 됩니다.

test_ 또는 _test 그리고 실행

pytest에 의해서 실행될 파일과 함수는 test_ 또는 _test를 앞이나 뒤에 붙여야 합니다.

그래서 test_가 붙은 파일의 폴더에서 pytest 를 실행하면 pytest는 test_가 붙은 파일과 파일내에 test_가 붙은 함수들을 실행합니다.

다음은 Chapter 1에서 설명한 Thread Safe 싱글톤 패턴 코드를 pytest로 실행한 결과입니다.

Unit Test 실행 결과

$ pytest 만 실행했습니다.

pytest code

Chapter 1에서 설명한 Thread Safe 싱글톤 패턴 코드를 pytest를 이용하여 수정하였습니다.

Singleton.py

이전과 같습니다.

from threading import Lock

class Singleton(type):
    _instance = None
    _lock = Lock()

    def __call__(cls, *args, **kwargs):
        with cls._lock:
            if not cls._instance:
                cls._instance = super().__call__(*args, **kwargs)
        return cls._instance

SingletonClass.py

다 같지만 if __name__ == ‘__main__’: 이 없어졌습니다.

이 코드를 pytest code로 옮기는 것이 핵심입니다.

from Singleton import Singleton

class SingletonClass(metaclass=Singleton):
    def __init__(self, name):
        self.name = name

    def __str__(self):
        return self.name;    

tests/test_SingletonClass.py

def run은 test_가 없기 때문에 pytest에 의해서 실행되지 않습니다.

test_Singleton은 pytest에 의해서 실행됩니다.

import sys
sys.path.append('..')
from SingletonClass import SingletonClass
from threading import Thread

def run(name, result):
    singleton = SingletonClass(name)
    result[0] = str(singleton)

def test_Singleton():
    resultA = ['A']
    resultB = ['B']
    processa = Thread(target=run, args=('ACLASS', resultA))
    processb = Thread(target=run, args=('BCLASS', resultB))
    processa.start()
    processb.start()
    assert(resultA[0] == resultB[0])

위 코드에서 추가 설명이 필요한 코드는 다음과 같습니다.

  • sys.path.append(‘..’)는 상위 폴더 모듈을 참조하고자 path에 해당 경로를 추가합니다.
  • resultA와 resultB가 List인 이유는 결과값을 thread process function으로부터 받아오고자 하면서 List는 Shallow copy가 되기 때문입니다. 즉, run안에서 resultA가 새로 생성되지 않고 test_Singleton내에 만들어진 resultA 객체가 그대로 전달되어 run 함수 안에서 list 내부의 값을 수정하는 것은 같이 수정됩니다.
  • assert (resultA[0] == resultB[0]) 은 True이면 pass, False이면 Fail을 표시합니다.

결과

결과는 test_SingletonClass.py에서 1개의 test case가 발견되어 pass하였습니다.

python에서 Unit Test 에 대한 개인적 의견

저는 python은 주로 command base의 program 특히 backend Program용으로 많이 사용합니다.

그렇다 보니 UI가 있는 Front End보다는 직접 손으로 해볼만한 UI는 없고 command로만 이루어집니다.

이전까지만 해도 if __name__ == “__main__”: 을 사용했습니다.

항상 파일 아래에 추가해서 사용했는데요.

다음의 문제들이 pytest를 사용함으로써 해결이 되었습니다.

즉, 반대로 pytest를 사용했을때에는 장점이 되겠죠?

  • 모든 파일을 일일히 실행해야 한다.
  • 일관된 test case를 만들지 않게 된다.
  • Test Code Coverage가 낮다.
  • import가 복잡해진다.

pytest로 대체했을때 단점은 딱 하나입니다.

  • 코딩할 때 시간이 더 걸린다.

하지만 앞으로 Rest API를 release하는데 있어 매우 중요한 포인트가 될것 같습니다.

Leave a Reply