저희팀(TmaxSoft, Compiler팀)은 컴파일러를 개발하고 있습니다. 컴파일러를 개발하다보면 코드의 수정 혹은 추가로 인해 기존에 잘 되던 것이 잘 안되는 문제가 빈번히 발생합니다. 언어를 처리하는 프로그램의 특성상 상호의존적인 코드의 비중이 높기 때문이죠.
그런 까닭에 “실용주의 프로그래머“에서도 강조하는 회귀 테스트가 정합성을 생명으로하는 컴파일러의 개발과정에서 빼놓을 수 없는 영역을 차지하게 됩니다.
저희팀에서 컴파일러 혹은 인터프리터의 개발과정에서 사용하는, Ruby로 작성된 회귀 테스트 장치(regression test harness) 코드는 다음과 같습니다. (Ruby의 맛만 살짝 본 상태에서 제가 작성한 조악한 코드지만, ‘이런식으로 회귀 테스트를 하기도 하는구나!’ 정도로 이해해주시면 좋겠네요.)
nameArray = Array.new
failArray = Array.new
read list
File.open(“list.txt”) do |file|
while name = file.gets
if (File.exist?(name.chomp + “.” + ezext))
nameArray.push(name.chomp)
else
puts “file #{name.chomp}.#{ezext} not found!”
end
end
end
run
nameArray.each do |name|
cmd = “ezp -i #{name}.#{ezext} > #{name}.tmp”
puts cmd
stdout = #{cmd}
cmd = “diff #{name}.tmp #{name}.out”
stdout = #{cmd}
if not stdout.empty?
failArray.push(name.chomp)
end
end
report
puts
if failArray.size() == 0
puts “regression test success!”
else
puts “regression test fail!”
puts “———————”
puts “total : #{nameArray.size()}”
puts “fail : #{failArray.size()}”
puts
puts “fail list”
puts “———————”
failArray.each do |name|
puts “#{name}.#{ezext}”
end
end
remove tmp files
rm *.tmp
컴파일러나 인터프리터가 제대로 동작하는지 확인하는 가장 간단한(?) 방법은 소스코드를 사용하여 예상대로 동작하는지 확인하는 것 입니다. 일반적인 경우에는 standard output(이하 stdout) 결과를 보고 이상유무를 파악합니다. 기본적인 아이디어는 여기서 정리하고 본론으로 들어가자면…
여기서 소개한 회귀 테스트를 위해서는 2가지 작업이 선행되어야 합니다.
1. 회귀 테스트에 포함시킬 예제 파일의 이름(확장자 제외)을 list.txt에 추가 (newline으로 여러개의 파일구분)
2. 정상 동작할때 stdout을 filename.out 파일에 저장 (e.g. ezp -i filename.ezt > filename.out)
회귀 테스트 과정은 다음과 같습니다.
1. list.txt에서 테스트 할 파일 이름을 추출하여 nameArray에 저장, 이 때 존재하는 파일인지 확인
2. ezp 인터프리터 실행하여 stdout을 filename.tmp에 저장
3. diff로 정상 동작시 결과 filename.out과 현재 실행 결과 filename.tmp를 비교
4. diff의 stdout이 비어 있으면 테스트 성공! 비어있지 않으면 failArray에 추가
5. 회귀테스트 결과 출력
저희팀에서는 (당연한 이야기겠지만) 저장소에 commit하기 전에 회귀 테스트를 통과하는 것을 정책적으로 강제하고 있습니다.
OS 과목에서 한 pintos 프로젝트도 비슷하게 되어 있었죠. 특히 쓰레드 매니저 만드는 부분에서는 한 테스트 통과시키면 잘 되던 테스트가 실패하고 이래서 매우 골치아팠던…ㅠㅠ;
OS에서 과제로 내주는 프로젝트가 그렇게 어렵다고 하던데, 저는 그걸 못해본게 조금 아쉽습니다. ^^;