14. 테스트 및 벤치마킹 패턴 🟢
학습 목표:
- Rust의 세 가지 테스트 계층: 단위(Unit), 통합(Integration), 문서(Doc) 테스트를 마스터합니다.
- **속성 기반 테스트(Property-based testing)**인
proptest로 예상치 못한 엣지 케이스를 찾아냅니다.- Criterion 라이브러리를 사용해 통계적으로 신뢰할 수 있는 성능 벤치마킹을 수행합니다.
- 무거운 프레임워크 없이 트레이트 기반의 모킹(Mocking) 전략을 구축합니다.
세 가지 테스트 계층
Rust는 언어 차원에서 강력한 테스트 도구를 제공합니다.
- 단위 테스트(Unit Tests): 비즈니스 로직과 같은 파일에 작성하며,
mod tests와#[cfg(test)]를 사용합니다. 비공개 함수도 테스트할 수 있습니다. - 통합 테스트(Integration Tests):
tests/디렉토리에 위치하며, 라이브러리의 공개 API만 테스트합니다. 외부 사용자의 관점에서 테스트를 진행합니다. - 문서 테스트(Doc Tests): 문서 주석(
///) 내의 코드 예제를 실제로 실행합니다. 문서와 실제 코드가 일치함을 보장합니다.
속성 기반 테스트 (Proptest)
특정 입력값 하나하나를 테스트하는 대신, "임의의 입력에 대해 항상 만족해야 하는 성질(Property)"을 테스트합니다.
#![allow(unused)] fn main() { proptest! { #[test] fn test_reverse_twice_is_identity(v in prop::collection::vec(any::<i32>(), 0..100)) { // 성질: 두 번 뒤집으면 원래와 같아야 함 assert_eq!(reverse(&reverse(&v)), v); } } }
장점: 수백 개의 랜덤한 입력을 자동으로 생성하며, 실패 시 최소한의 재현 케이스로 데이터를 축소(Shrink)해 줍니다.
Criterion을 이용한 정밀 벤치마킹
cargo bench보다 훨씬 정밀한 성능 측정이 가능합니다. 통계적 유의미함을 검사하고 HTML 리포트를 생성해 줍니다.
#![allow(unused)] fn main() { fn bench_fibonacci(c: &mut Criterion) { c.bench_function("fibonacci 20", |b| b.iter(|| fibonacci(black_box(20)))); } }
팁:
black_box를 사용하여 컴파일러가 벤치마크 함수를 아예 생략하거나 상수화해 버리는 과도한 최적화를 방지하세요.
프레임워크 없는 모킹(Mocking) 전략
Rust에서는 복잡한 모킹 프레임워크 대신 트레이트 기반의 의존성 주입을 권장합니다.
#![allow(unused)] fn main() { trait Clock { fn now(&self) -> Instant; } // 실무용 구현체 struct RealClock; // 테스트용 가짜 구현체 struct MockClock { fixed_time: Instant } }
이러면 테스트할 구조체에 Clock 트레이트만 요구하면 실제 환경과 테스트 환경에서 각각 다른 객체를 넣어줄 수 있습니다.
📝 연습 문제: 속성 기반 테스트 실습 ★★ (~25분)
SortedVec<T>라는 항상 정렬 상태를 유지하는 래퍼를 만드세요. proptest를 사용하여 다음 성질들을 검증해 보세요:
- 어떤 값을 넣어도 내부 벡터는 항상 정렬되어 있는가?
contains()결과가 표준Vec::contains()와 일치하는가?- 삽입한 만큼 길이가 늘어났는가?
📌 요약
- 문서 테스트를 적극 활용해 살아있는 문서를 만드세요.
- 복잡한 엣지 케이스가 걱정된다면 속성 기반 테스트가 정답입니다.
- Criterion 리포트를 통해 성능 변화를 시각적으로 추적하세요.
- 모킹보다는 트레이트 설계를 통해 테스트 가능한 구조를 만드세요.