파이썬은 참 좋은 언어인데, C나 C#같이 컴파일 방식이 아니라 인터프리터 방식이다보니 실제 코드가 실행되는 순간에 문법적인 오류가 발생하면 죽는 문제가 생깁니다. 컴파일 방식이라면 실행하기 전에 인지할 수 있겠지만 파이썬은 쉽지가 않습니다. 이 문제를 해결하기 위해서는 수정한 부분을 실제 조건을 만족시켜 돌려봐야하는데요. 이것도 쉬운 것은 아닙니다. 실제 조건문이 만족하도록 설정하는 행위가 소스를 수정하는 것이기 때문에 이 또한 문제를 야기할 수 있습니다.

 

이런 문제를 풀기 위하여 보통 유닛테스트를 많이 사용하는데요. 유닛테스트를 전체 프로그램을 test할 수 있으면 수정시 발생하는 오류를 가장 빨리 발견할 수 있습니다. 하지만 소스 전체를 유닛테스트로 적용하기 위해서는  최초 설계부터 반영을 해야하기 때문에 쉽지 않습니다.

 

결론적으로 유닛테스트를 적용하면 좋은데, 전체 소스코드에 적용하기에는 좀 어렵다인데요. 대안으로 특정 함수만 별도로 유닛테스트한 후 사용하는 것입니다.

 

이번에 주식 자동매매 프로그램을 만들던 중 매매 가격에 따라 유효한 주문 가격을 결정하는 함수를 유닛테스트를 적용한 예를 설명하도록 하겠습니다. 특히 주식 자동매매에서 유효한 주문가격을 구하지 못하면 주문 거부가 발생하기 때문에 꼼꼼하게 test해보아야 합니다. 모든 case를 손으로 직접 test하는 것은 쉬운 일이 아니기 때문에 유닛테스트를 적용하는 것이 바람직 합니다. 특히 해당 함수 내부 소스가 변경되는 경우에는 유닛테스트의 진가가 발휘됩니다.

 

올해 변경된 주식 가격별 호가 단위입니다. 자동매매 코드에서는 소숫점 계산을 한 목표 매수가가 나오고 이를 버림/반올림/올림 등의 조건을 추가하여 호가 단위에 맞는 가격으로 변경해주는 함수가 필요합니다.

우선 아래 package를 import합니다.

 

import unittest

 

그리고 원하는 함수를 작성합니다. 일단 2천원 미만은 1원 단위의 가격을 돌려주는 함수를 만들어봅시다. 

 

def get_order_price(price) :
    tick_size = 0
    if price < 2000:
            tick_size = int(price)
    return tick_size


다음은 unittest를 사용하는 방법입니다. unittest에서는 아래와 같은 CustomTests class를 지원합니다. 원하는 함수안에 필요한 코드를 넣으면 됩니다. test case는 test_process_statement() 함수 안에서 작성하면 됩니다.

 

 

 

class CustomTests(unittest.TestCase): 
    def setUp(self):    # 무시
        pass
    def tearDown(self): # 무시
        pass
    def test_process_statement(self) :
        in_price = 45
        result = get_order_price(in_price)
        self.assertEqual(result, 45) # 계산 결과와 원하는 값 입력

 

위와 같이 test_process_statement() 함수 안에 test case를 만들면 됩니다. 그 후 unittest.main()을 호출하면 계산값과 원하는 결과가 틀리면 self.assertEqual함수에서 fail이 뜹니다.

 

unittest.main()

 

실행을 해보면 아래와 같은 결과 나옵니다.

 

 

만약 in_price와 다른 값을 입력한다면

 

    def test_process_statement(self) :
    	in_price = 45
        result = get_order_price(in_price)
        self.assertEqual(result, 44) # 계산 결과와 다른 값 입력

 

친절하게도 틀리다는 메세지가 출력됩니다.

 

 

다음은 주식 거래시 사용하는 실제 코드입니다. 이번 전략에서는 목표 가격이 오면 가능한 빨리 매수하기를 원하기 때문에 버림을 적용합니다. float를 int로 변경하기만 하면 자동으로 버림이 적용되기 때문에 간단하게 구현할 수 있습니다.

 

def get_order_price(price):
    tick_size = 0.0
    if price >= 500000:
        tick_size = int(price/1000)*1000
    elif price >= 100000:
        tick_size = int(price/500)*500
    elif price >= 50000:
        tick_size = int(price/100)*100
    elif price >= 10000:
        tick_size = int(price/50)*50
    elif price >= 5000.0:
        tick_size = int(price/10)*10
    elif price >= 1000.0:
        tick_size = int(price / 5)*5
    else:
        tick_size = int(price)
    return tick_size

 

위 코드를 바탕으로 test case를 만들어보면 아래와 같습니다. 이외에도 모든 가격대를 test하는 문장을 추가하여야합니다.

 

    def test_process_statement(self) :
        result = get_order_price(999.99) # 1000원 미만 test
        self.assertEqual(result, 999)

        result = get_order_price(1000.01) # 1000원 이상 test
        self.assertEqual(result, 1000)

        result = get_order_price(4999.9999) # 5000원 이하 test
        self.assertEqual(result, 4995)

        result = get_order_price(9999.9999) # 10000원 이하 test
        self.assertEqual(result, 9990)

 

 

실행결과는 아래와 같습니다. 즉 문제가 없다는 의미입니다.

 

 

만약 함수의 출력과 예상결과가  틀린 경우에는 오류 메세지가 뜹니다. 오류 메세지가 뜨면 해당 가격에 대한 코드를 자세하게 살펴보면서 문제점을 수정하면 됩니다.

 

 

이렇게 완성된 함수를 본 프로그램에 추가하여 개발을 진행하면 됩니다. 모든 함수를 유닛테스트하기에는 시간이 많이 걸리는 문제가 있으므로, case가 복잡하거나 경우의 수가 많은 함수를 만드는 경우에는 해당 함수만 뽑아서 유닛테스트 환경하에서 test하는 습관을 들이는 것이 좋을 듯 합니다. 당장은 힘들겠지만 시간이 가면 갈수록 코드의 안정성을 유지하는 것이 유닛테스트 통과한 코드입니다.

 

반응형

설정

트랙백

댓글