Python trace 모듈이란?
Python 표준 라이브러리의 trace 모듈은 프로그램이 실행되는 동안 어떤 문(statement)이 실행되는지 한 줄씩 추적하고, 문 단위 실행 커버리지(statement coverage)를 분석할 수 있게 해주는 도구입니다. 또한 프로그램 실행 중 호출된 함수 목록을 확인하거나, 함수 간 호출 관계(caller relationship)까지 시각적으로 파악할 수 있는 기능도 제공합니다.
이 글에서는 두 개의 간단한 Python 스크립트 예제를 통해 trace 모듈의 핵심 기능을 하나씩 살펴보겠습니다.
예제 파일 1: myfunctions.py
#myfunctions.py
import math
def area(x):
a = math.pi*math.pow(x,2)
return a
def factorial(x):
if x==1:
return 1
else:
return x*factorial(x-1)
예제 파일 2: mymain.py
#mymain.py
import myfunctions
def main():
x = 5
print ('area=',myfunctions.area(x))
print ('factorial=',myfunctions.factorial(x))
if __name__=='__main__':
main()
명령줄 인터페이스(CLI)로 trace 모듈 사용하기
trace 모듈은 별도의 코드 작성 없이 명령줄에서 바로 실행할 수 있습니다. 모듈의 모든 기능은 명령줄 옵션으로 호출할 수 있으며, 그중 가장 중요한 옵션이 --trace입니다. 이 옵션은 프로그램이 실행되는 대로 각 소스 코드 줄을 화면에 출력해 줍니다. 아래 예제에서는 추적 결과 생성 시 특정 디렉터리를 제외하는 --ignore-dir 옵션도 함께 사용했습니다.
E:\python37>python -m trace --ignore-dir=../lib --trace mymain.py
실행 결과
mymain.py(2): def main():
mymain.py(7): if __name__=='__main__':
mymain.py(8): main()
--- modulename: mymain, funcname: main
mymain.py(3): x=5
mymain.py(4): print ('area=',myfunctions.area(x))
--- modulename: myfunctions, funcname: area
myfunctions.py(3): a=math.pi*math.pow(x,2)
myfunctions.py(4): return a
area= 78.53981633974483
mymain.py(5): print ('factorial=',myfunctions.factorial(x))
--- modulename: myfunctions, funcname: factorial
myfunctions.py(6): if x==1:
myfunctions.py(9): return x*factorial(x-1)
--- modulename: myfunctions, funcname: factorial
myfunctions.py(6): if x==1:
myfunctions.py(9): return x*factorial(x-1)
--- modulename: myfunctions, funcname: factorial
myfunctions.py(6): if x==1:
myfunctions.py(9): return x*factorial(x-1)
--- modulename: myfunctions, funcname: factorial
myfunctions.py(6): if x==1:
myfunctions.py(9): return x*factorial(x-1)
--- modulename: myfunctions, funcname: factorial
myfunctions.py(6): if x==1:
myfunctions.py(7): return 1
factorial= 120
출력 결과를 보면 재귀 함수인 factorial()이 x 값이 1이 될 때까지 반복적으로 자기 자신을 호출하는 과정까지 그대로 추적되는 것을 확인할 수 있습니다.
--count 옵션: 문 실행 횟수 집계
--count 옵션을 사용하면 실행에 참여한 각 모듈마다 .cover 확장자를 가진 파일이 생성됩니다. 이 파일에는 각 문이 실제로 몇 번 실행되었는지가 기록되어 있어, 어떤 코드가 자주 실행되고 어떤 코드가 전혀 실행되지 않았는지 한눈에 파악할 수 있습니다.
E:\python37>python -m trace --count mymain.py area= 78.53981633974483 factorial = 120
생성된 myfunctions.cover 파일의 내용은 다음과 같습니다. 각 줄 앞의 숫자가 해당 문의 실행 횟수를 의미합니다.
1: import math 1: def area(x): 1: a = math.pi*math.pow(x,2) 1: return a 1: def factorial(x): 5: if x==1: 1: return 1 else: 4: return x*factorial(x-1)
mymain.cover 파일의 내용은 다음과 같습니다.
1: import myfunctions
1: def main():
1: x = 5
1: print ('area=',myfunctions.area(x))
1: print ('factorial=',myfunctions.factorial(x))
1: if __name__=='__main__':
1: main()
--summary 옵션: 커버리지 요약 보고서
--summary 옵션은 --count 옵션과 함께 사용할 때, 모듈별 전체 문 수와 커버리지 백분율을 간결한 표 형태로 요약해 보여줍니다.
E:\python37>python -m trace --count --summary mymain.py area = 78.53981633974483 factorial = 120 lines cov% module (path) 8 100% myfunctions (E:\python37\myfunctions.py) 7 100% mymain (mymain.py)
위 결과에서 두 모듈 모두 100%의 커버리지를 기록한 것을 알 수 있습니다. 즉, 작성된 모든 문이 최소 한 번 이상 실행되었다는 의미입니다.
--file 옵션: 여러 실행에 걸친 누적 카운트
--file 옵션은 여러 번의 추적 실행에 걸쳐 실행 횟수를 누적 저장할 파일의 이름을 지정합니다. 아래 예제에서는 첫 번째 실행 시점에는 report.txt 파일이 아직 존재하지 않는다는 안내 메시지가 출력되지만, 이후 실행부터는 실행 횟수가 해당 파일에 계속 누적됩니다.
E:\python37>python -m trace --count --file report.txt mymain.py area = 78.53981633974483 factorial = 120 Skipping counts file 'report.txt': [Errno 2] No such file or directory: 'report.txt' E:\python37>python -m trace --count --file report.txt mymain.py area= 78.53981633974483 factorial= 120
--listfuncs 옵션: 호출된 함수 목록 확인
--listfuncs 옵션은 프로그램 실행 중 호출된 모든 함수의 목록을 파일명, 모듈명, 함수명과 함께 표시합니다.
E:\python37>python -m trace --listfuncs mymain.py | findstr -v importlib area= 78.53981633974483 factorial= 120 functions called: filename: E:\python37\lib\encodings\cp1252.py, modulename: cp1252, funcname: IncrementalEncoder.encode filename: E:\python37\myfunctions.py, modulename: myfunctions, funcname: <module> filename: E:\python37\myfunctions.py, modulename: myfunctions, funcname: area filename: E:\python37\myfunctions.py, modulename: myfunctions, funcname: factorial filename: mymain.py, modulename: mymain, funcname: <module> filename: mymain.py, modulename: mymain, funcname: main
--trackcalls 옵션: 함수 호출 관계 분석
--trackcalls 옵션은 --listfuncs 옵션과 함께 사용하며, 단순히 호출된 함수 목록을 넘어 어느 함수가 어느 함수를 호출했는지에 대한 호출 관계(calling relationships)까지 생성해 보여줍니다.
E:\python37>python -m trace --listfuncs --trackcalls mymain.py | findstr -v importlib area= 78.53981633974483 factorial= 120 calling relationships: --> E:\python37\myfunctions.py *** E:\python37\lib\trace.py *** --> mymain.py trace.Trace.runctx -> mymain.<module> *** E:\python37\myfunctions.py *** myfunctions.factorial -> myfunctions.factorial *** mymain.py *** mymain.<module> -> mymain.main --> E:\python37\lib\encodings\cp1252.py mymain.main -> cp1252.IncrementalEncoder.encode --> E:\python37\myfunctions.py mymain.main -> myfunctions.area mymain.main -> myfunctions.factorial
출력 결과를 통해 mymain.main이 myfunctions.area와 myfunctions.factorial을 호출했고, factorial 함수는 재귀적으로 자기 자신을 호출했다는 사실을 명확히 확인할 수 있습니다.
마무리
Python의 trace 모듈은 별도의 서드파티 도구 설치 없이 표준 라이브러리만으로 프로그램의 실행 흐름 추적, 문 커버리지 분석, 함수 호출 관계 파악이 가능한 강력한 진단 도구입니다. 디버깅은 물론, 테스트 코드가 소스를 얼마나 충분히 검증하고 있는지 점검하는 용도로도 유용하게 활용할 수 있습니다.