Python/2.7 information

유닛테스트.

qkqhxla1 2017. 3. 26. 20:48

a.py가 있고 a.py안에 어떤 한 서비스를 돌리는 코드가 있다고 가정하자.


a.py

# -*- coding: utf-8 -*-

class Service():
    def service_run(self, name, age, phone_number):
        c = [name,age,phone_number]
        a = self.process(c)
        # 어떤 동작들을 더 해야 한다고 가정.
        self.get_in_db(a[0], a[1], a[2]) # db로 넣는 함수가 있다고 가정.

    def process(self, p):
        return [p[0], p[1], p[2]]

    def get_in_db(name, age, phone_number): # db로 넣는다고 가정.
        pass

if __name__=='__main__':
    while True:
        #어떤 동작을 하여 person_list를 받아온다고 가정
        person_list = [['John','17','1234-5678'], ['Jack','20','010-2323-2323']]
        service = Service()
        for p in person_list:
            service.service_run(p[0], p[1], p[2])

클래스 안에 어떤 결과값을 db로 넣는 함수가 있다고 하자. 케이스 몇개를 넣어서 함수를 테스트해보고 

싶은데 어떻게 할수 있을까? 물론 person_list를 새로 만들거나 뒤에 데이터를 추가하고, get_in_db()

함수를 호출하기 전에 print등으로 name, age, phone_number등을 출력해서 테스트할수 있을 것이다.



하지만 이 경우 실수로 원본 소스코드를 훼손시킬수도 있고, 테스트의 일부 잔해가 남을수 있으며,(ex)주석이라던지.) 위코드처럼 db에 데이터가 들어가는 경우 테스트용 데이터가 가공되어 db로 들어갈 수도 있다.


뭔가 일부 수정이 필요해서 가볍게 테스트를 해볼경우에도 어떤식으로 데이터가 들어가는지, 몇개가 들어가는지 등을 매번 분석해야 한다. 하지만 첫 코드작성자가 유닛테스트를 만들어놓으면.


1. 실수로 원본 소스코드를 훼손시킬 가능성이 낮아지고 테스트의 잔해가 남을 가능성이 없다.


2. 한번 만들어놓으면 구조파악이 쉬워 재사용하기 쉽다.


등이 있는것 같다.



처음 보는 사람이 위의 a.py에 데이터 ['chulsu', '7', '1234-454']라는 걸 테스트하고 싶다고 가정해보자.


위 코드는 짧아서 파악하기 쉽지만 유닛테스트가 없을 경우 이 데이터를 넣으려면 디버깅을 하던 해서 

person_list가 우리가 원하는 데이터의 집합이란걸 알테고, 여기에 더럽게 .append로 데이터를 추가한 후

get_in_db()라는 함수가 db에 넣는 함수니까 이 전에 get_in_db에 들어가는 인자들을 출력해보면서

디버깅이 가능하다.


만약 a.py같은 소스코드가 여러개이고, a.py가 길다고 가정하면 매번 들어가서 데이터가 어디에 있는지

보고, db에 들어가는 형식이 어떤지 등등을 다 살펴봐야 한다.



편하게 테스트를 할수 있게 만들어보자. 아래는 템플릿처럼 가져다가 쓸수 있는 test.py이다.

test.py

# -*- coding: utf-8 -*-
import unittest

class TestBase(unittest.TestCase):
    @staticmethod
    def replace_function(method):

        def print_parameter(*args):
            print 'start test!'
            for parameter in args:
                print parameter
            print 'end test!\n'

        setattr(method.__self__, method.__name__, print_parameter)

test.py의 코드설명을 하자면 굳이 인스턴스를 만들지 않고 사용하기 위해 메소드를 static하게 만들었다.

그리고 replace_function함수를 정의하는데, 이 메소드 내부에 print_parameter라는 함수를 선언했다.


print_parameter함수의 역할은 인자로 받은 것들을 하나씩 출력해주고 끝난다. 선언이 끝나고, 맨 아래에

setattr로 replace_function메소드의 인자로 받은 메소드의 이름을 바로 위에서 정의한 print_parameter로

바꿔준다.


후킹같은거라고 보면 되는데 이런걸 monkeypatch라고 부른다. 이렇게 setattr로 이름을 바꿔주게 되면

replace_function에 인자로 들어온 메소드를 실행시킬때 실제 그 메소드를 실행시키는게 아니라 print_parameter을

실행시켜 인자만 출력하고 끝내게 된다.


그리고 이 test.py를 import한 아래의 a_test.py 를 만든다.

a_test.py

import test
from a import Service

class ServiceTest(test.TestBase):
    def test_module(self):
        service = Service()
        self.replace_function(service.get_in_db)

        test_data = [
            ['chulsu', '7', '1234-454'],
            ['gogo', '77', '9876-454'],
        ]

        for p in test_data:
            service.process(p)
            service.get_in_db(p)

test.unittest.main()

test에서 후깅할수 있는 메소드를 가져왔고, Service를 맨 위의 a.py에서 가져왔다. 그리고 유닛테스트를

실행시키기 위해 만든 첫번째 함수를 테스트하기위해 test_module라는 이름을 붙였다.(이름이 test로

시작하면 유닛테스트 가능. 참조)


함수 안에서는 Service인스턴스를 만들고, 우리는 테스트만 하고 db에 실제로 데이터가 들어가지는

않기를 원하니까 service.get_in_db함수를 후킹해서 인자만 출력하도록 하고 실제 db로는 들어가지 않도록

한다.


이후 test_data에 테스트하고싶은 데이터를 넣고 하나하나 빼와서 실제로 일하는것처럼 process와

get_in_db를 실행시켜주게되면 실제 프로세스가 돌아가지만 디비에 넣기 전에 값이 나옴으로서 간단한

테스트가 가능해진다.



구조를 이해하고 유닛테스트를 한다고 가정해보자. 어떤 데이터를 실행시키고 싶으면 a_test.py의

test_data리스트에 추가하기만 하면 된다. 실제 동작코드에 뭔가 흠집이 날 가능성도 없고 디비에 이상한

값이 들어갈 가능성도 전혀 없다. a_test.py를 실행시키면 아래와 같은 테스트 화면이 나온다.

print_parameter함수 내부에 뭔가 더 넣어서 더 예쁘게 출력한다던지 하는게 가능하다.


뭔가 틀린 개념이 있거나 하면 지적해주시면 감사하겠습니다.

'Python > 2.7 information' 카테고리의 다른 글

mongo db  (0) 2017.04.19
로그 찍기.  (0) 2017.03.30
unittest. 테스트  (0) 2017.02.25
python smtp(메일보내기)  (0) 2016.12.14
python 딕셔너리에 관하여, 파이써닉하게 코드 짜기  (0) 2016.11.24