느린 테스트 스위트의 문제점
느린 테스트 스위트는 팀 사기 저하, 지속적인 통합(CI) 빌드 실패 시 책임 공방, 디버깅 시간 증가, 그리고 궁극적으로 테스트 자체에 대한 반감으로 이어집니다. 마치 서서히 끓는 물 속의 개구리처럼, 테스트 스위트의 속도가 점진적으로 느려지는 것을 인지하지 못하다가 큰 문제에 직면하게 됩니다. 이는 개발자들이 인터넷 서핑 등으로 시간을 보내게 만들고, 코드 품질 저하로 이어질 수 있습니다.
테스트 속도 향상 전략 (습관 변경 없이)
개발자의 테스트 작성 습관을 바꾸지 않고도 테스트 속도를 개선할 수 있는 기술적 방법들이 제시됩니다:
-
멀티 코어 활용: 컴퓨터의 코어 수가 증가함에 따라
Parallel Tests,Hydra,Deep Test와 같은 도구를 사용하여 테스트를 병렬로 실행합니다. 이 방식은 각 코어에 개별 데이터베이스를 설정해야 하는 경우가 많으며, 테스트 실행 시간을 지능적으로 분배하는 ‘비균등 분할’ 기능이 중요합니다. -
I/O 최적화: 개발 환경에서는 데이터베이스의
Journal Writeback기능을 비활성화하고, 파일 시스템의atime(접근 시간 기록) 기능을 끄면 불필요한 디스크 쓰기 작업을 줄여 I/O 성능을 개선할 수 있습니다. 단, 프로덕션 환경에서는 절대 비활성화해서는 안 됩니다. -
Fast Context:
Shoulda와 같은 프레임워크에서setup블록과 여러should블록을 한 번에 실행하여 중복되는 셋업 시간을 절약합니다. 단, 테스트의 원자성이 깨지지 않도록 주의해야 합니다. -
Ruby Enterprise Edition GC 설정:
Ruby Enterprise Edition을 사용하는 경우, 테스트 실행 중 가비지 컬렉션(GC)을 거의 수행하지 않도록 GC 설정을 극단적으로 높게 조정하면 빌드 시간을 크게 단축할 수 있습니다. 테스트는 많은 객체를 생성하고 버리므로, GC가 병목이 될 수 있습니다. -
시스템 프로파일링: CPU 및 I/O 성능 그래프를 모니터링하여 테스트 실행 중 발생하는 병목 현상을 식별하고 개선 기회를 찾아야 합니다.
테스트 품질 향상 전략 (습관 변경 필요)
근본적인 테스트 속도 및 신뢰성 개선을 위해서는 테스트 작성 습관을 개선해야 합니다:
-
모킹(Mocking) 및 스터빙(Stubbing): 외부 API 호출, 데이터베이스 접근, 파일 시스템 조작 등 비용이 많이 들거나 불안정한 작업을 모킹/스터빙하여 테스트를 격리하고 속도를 높입니다. 외부 API 테스트는 별도의 스위트로 분리하여 실행하는 것이 좋습니다.
-
셋업(Setup) 최적화: 중첩된
describe/context블록 내의setup남용을 경계해야 합니다. 불필요하게 많은 객체를 생성하는 셋업은 테스트 시간을 늘리고 가독성을 해칩니다. 많은 셋업이 필요한 테스트는 객체 설계에 문제가 있다는 신호일 수 있습니다. -
Factory Girl 활용:
Factory Girl사용 시create대신build를 사용하여 데이터베이스 저장 시간을 절약합니다. 또한, 최소한의 유효성 검사만 통과하는 ‘작은’ 객체 팩토리를 우선적으로 사용하고, 필요한 경우에만 ‘중간’ 또는 ‘큰’ 객체를 사용하도록 합니다. -
테스트를 통한 설계 개선: 많은 셋업이 필요한 테스트는 객체 설계에 문제가 있다는 신호이므로, 이를 통해 더 나은 객체 설계를 고민해야 합니다.
-
‘테스트 입양’ 프로그램: 프로파일링을 통해 가장 느리거나 문제가 많은 테스트를 식별하고, 여유 시간에 점진적으로 개선해 나가는 ‘테스트 입양’ 프로그램을 운영합니다. 이는 한 번에 모든 것을 고치려다 좌절하는 것을 방지합니다.
Metric Fu를 활용한 코드 품질 관리
Metric Fu는 코드 품질을 분석하는 다양한 도구(Flog, Fle, Cycro, Reek, Rails stats, Archov, Churn 등)를 통합한 Gem입니다. 이를 통해 코드의 문제점을 시각적으로 파악하고 개선할 수 있습니다:
-
Fle (구조적 중복 탐지): 코드 내의 중복된 구조를 찾아내 DRY(Don’t Repeat Yourself) 원칙 위반을 감지합니다. 이는 메서드 추출이나 모듈화를 통해 해결할 수 있습니다.
-
Churn (코드 변경 빈도): 자주 변경되는 ‘휘발성 코드’를 식별합니다. 휘발성 코드는 테스트가 잘 되어 있고 복잡도가 낮아야 합니다. 또한, 시스템 전반에 걸쳐 변경되는 ‘God Object’를 감지하는 데 유용합니다.
-
Archov (코드 커버리지): 코드 커버리지를 측정하지만, 높은 커버리지가 항상 좋은 테스트를 의미하지는 않습니다. 복잡하고 변경이 잦은 코드 영역에 커버리지를 집중하는 것이 중요합니다.
-
Flog & Cycro (복잡도 측정):
Cycro는 순환 복잡도를,Flog는 분기, 할당, 호출 등을 종합하여 메서드 복잡도를 측정합니다.Flog점수 60 이상은 메서드를 분리하여 리팩토링할 필요가 있음을 시사합니다. -
Reek, Raudy, Rails Best Practices (디자인 문제 감지):
Feature Envy,Long Method등 일반적인 디자인 스멜을 감지합니다. 단, 이러한 도구의 조언은 기계적인 판단이므로 항상 옳지는 않으며, 팀의 상황과 맥락에 맞게 해석해야 합니다. -
Metric Fu의 미래: 여러 메트릭을 조합하여 코드의 ‘핫스팟’ (예: 변경이 잦고, 복잡하며, 커버리지가 낮은 메서드)을 식별하는 ‘메타메트릭스’ 기능 개발을 목표로 합니다.