소스코드의 Logic은 무엇으로 검증 할 수 있을까?

개발자의 PT나 그림? 혹은 소스코드를 직접 봐야할까?

이를 테스트한 데이터로 검증하는 것이 테스트 주도 개발(Test-driven development), TDD이다.

TEST-DRIVEN DEVELOPMENT

예를 들어보겠다.

다음 코드는 캘린더의 특정 이벤트의 ics 파일에서 필요한 필드값을 추출하는 함수이다.

# Extract event name, start/end time, location from requesting ics file
def icsParser(homeset_cal_id, cal_id, evt_id, req_headers):
	# file open
	url_resp = requests.request("GET",homeset_cal_id+cal_id+ics,headers=req_headers)
	ics_data = str(url_resp.text)
	
	# Get Basic Info
	evt_name = ics_data[ics_data.find("SUMMARY:")+8:ics_data.find("DTSTART;")]
	evt_start_dt = ics_data[ics_data.find("DTSTART;")+8:ics_data.find("DTSTAMP:")]
	evt_end_dt = ics_data[ics_data.find("DTEND;")+6:ics_data.find("SUMMARY:")]
	
	# Convert dt from ICS to DB format
	evt_start_dt = dtConverter(evt_start_dt)
	evt_end_dt = dtConverter(evt_end_dt)

	# Duplicated data remove from ics file
	evt_loc = None
	tmp_ics_data = ics_data.replace("-LOCATION","") # Except X-LIC-LOCATION id column
	if tmp_ics_data.find("LOCATION:") is not -1:
		evt_loc = tmp_ics_data[tmp_ics_data.find("LOCATION:")+9:tmp_ics_data.find("SEQUENCE:")]

	return evt_name, evt_start_dt, evt_end_dt, evt_loc

이 함수를 아래와 같이 json 방식으로 반환하도록 변경하고, iCalendar 라이브러리를 활용해 코드의 안정성을 높혔다.

# Convert ics to json format
def convert_ics_to_json(homeset_cal_id, evt_id, acc_auth): # acc_auth="Basic user_base64_hash_key"
    # Open .ics
    url_resp = requests.request("GET",homeset_cal_id+evt_id,headers={"Depth":"1","Authorization":"Basic "+acc_auth})
    
    # Read Calendar format
    cal = Calendar.from_ical(url_resp.text)

    # Extract event field data
    for component in cal.walk():
        if component.name == "VEVENT":
            evt_name=component.get('summary')
            dt_start=str(component.get('dtstart').dt)
            dt_end=str(component.get('dtend').dt)
            location=component.get('location')

            dt_start=dtConverter(dt_start)
            dt_end=dtConverter(dt_end)

            return json.dumps({
                    'name': evt_name,
                    'dt_start': dt_start,
                    'dt_end': dt_end,
                    'location': location
                })
    return "error"

이렇게 새로운 logic을 구현했을 때, 이전 기존 코드의 데이터를 해치지 않는 것을 regression이라 한다.

이 regression을 지킬 수 있도록 새로운 logic이 레거시 코드의 성능을 완벽히 보장하고,

추가적인 입력에 대해서도 어떤 성능을 보장하는지 알 수 있는 방법이 있다.

미리 테스트케이스를 정해놓고, 그 테스트케이스를 수행하는 모듈을 개발하는 것이다.

새로 추가되는 예외상황에 대해서도 테스트케이스 리스트에 반영을 하고,

이 모듈은 예측값까지 출력하도록 하여 테스트의 성공 유무를 바로 확인할 수 있다.

먼저, 테스트케이스의 항목을 정한다.

1. 콜론이나 특수문자 여부
- #, $, %, ☆ 등 기본 특수문자
- (웃음) (big grin) (슬픈) 등 특정 OS, 앱, 키보드만 가진 특수문자
- 유니코드, 세미콜론, 콜론처럼 ics 파일 포맷을 해칠 수 있는 특수문자

2. 이벤트명 길이에 따른 여부
- 너무 긴 경우 ( 50자 / 100자 / 150자 / 200자 / 255자 )

3. 외국어 입력 여부
- 일본어
- 중국어(한자)
- 스페인어
- 한글 자/모음 조합

4. 시간 입력이 없는(안되는) 경우
- 연속된 날짜 입력

5. 반복 입력을 하는 경우
- 횟수에 따라 반복 입력을 하는 경우
- 기간에 따라 반복 입력을 하는 경우
- 반복 이벤트 중, 특정 이벤트 하나의 값만 변경하는 경우

다음, 그 항목에 맞는 여러 테스트케이스를 아래 그림과 같이 작성한다

IMG

그리고 이를 비교해서 보여주는 테스트 모듈을 따로 작성한다.

from caldavclient import util
from caldavclient import CaldavClient
import json 
import time

""" LOGIN SETTING START """
with open('key.json') as json_data:
    d = json.load(json_data)
    userId = d['iCal']['id']
    userPw = d['iCal']['pw']
    auth = d['iCal']['auth']

hostname = "https://caldav.icloud.com"

""" END"""

client = CaldavClient(
    hostname,
    userId,
    userPw
)

# Get calendar data
calendars = client.getPrincipal().getHomeSet().getCalendars()

# Assign for time check
ics_ls=[]

for calendar in calendars:
	print("Calendar Name : ",calendar.calendarName, "----------------")
	
	# Remove duplicated filed
	homeset_hostname, dummy= client.getPrincipal().getHomeSet().homesetUrl.split(':443/')
	
	# Extract data from each event
	events = calendar.getAllEvent()
	for event in events:
		if event.eventUrl.find(".ics") is 64:
			ics_ls.append([homeset_hostname+":443",event.eventUrl])

			# ADAPT TESTCASE
			print(util.convert_ics_to_json(homeset_hostname+":443",event.eventUrl,auth))
			print("{\"name\": \"XXXX\", \"dt_start\": \"201X-XX-XX XX:XX:XX+09:00\", \"dt_end\": \"201X-XX-XX XX:XX:XX+09:00\", \"location\": XXX or null}")
			print()
			print() # for easy readable


# Check time cost
start_time = time.time()
ics_cnt=0
for ics in ics_ls:
	util.convert_ics_to_json(ics[0],ics[1],auth)
	ics_cnt = ics_cnt+1
end_time = time.time()

print("Total ICS count",ics_cnt," , Time Cost : ",end_time-start_time)

테스트케이스 검출을 위한 소스코드다.

주목할만한 부분은 #ADAPT TESTCASE 부분.

util.convert_ics_to_json()값과 예상 포맷을 같이 출력한다.

콘솔창은 다음과 같이 출력된다.

IMG

조금 더 개선한다면, 위에서 작성한 테스트케이스 내용을 비교하여 True/False로 나타내고 테스트케이스 ID를 출력하는 기능이 포함될 것이다.

이와 같이 테스트케이스를 이용하면, 작성한 함수의 기능을 검증할 수 있다.

또한, 함수 자체가 변경이 되더라도 이전의 Performance가 보장되는지 TDD를 통해 공식적으로 검증이 가능하다.