러스트는 신뢰성과 안전성에 중점을 두고 설계되었기 때문에 모듈 테스트는 언어 생태계의 필수적인 부분이 되었습니다. 테스트는 언어 및 툴체인 수준에서 통합되어 (cargo test) 개발 프로세스의 유기적인 부분이 됩니다.
문제의 역사
전통적인 생태계(예: C/C++, Python, Java)에서는 테스트가 프로그램과 별개로 존재했습니다. 하지만 러스트에서는 테스트가 코드의 일부이며, 완전한 모듈로 컴파일되고 검증됩니다. 이러한 시너지는 언어 구조와 컴파일러의 특성 덕분에 이루어집니다.
문제
올바른 테스트 없이는 주요 기능의 신뢰성을 보장할 수 없습니다. 복잡하고 다수의 모듈로 구성된 프로젝트에서는 테스트를 편리하게 구성하고, 다른 모듈의 상태에 의존하지 않으면서 프로젝트 구조를 복잡하게 만들지 않는 방법이 필요합니다.
해결책
러스트에서는 테스트를 소스 파일 내에 배치하거나 (#[cfg(test)] 사용) 통합 테스트를 위한 tests라는 별도 폴더에 배치할 수 있습니다. 각 모듈에 대해 비공식 테스트를 추가할 수 있고, 이는 비공식 API에 접근할 수 있습니다.
코드 예:
pub fn add(a: i32, b: i32) -> i32 { a + b } #[cfg(test)] mod tests { use super::*; #[test] fn test_add_positive() { assert_eq!(add(2, 3), 5); } #[test] fn test_add_negative() { assert_eq!(add(-1, -3), -4); } }
주요 특징:
모듈 테스트에서 use super::*;는 무엇을 위한 것인가요?
테스트가 현재 모듈의 함수 및 구조체(비공식 포함)에 접근할 수 있도록 하기 위해, 테스트에서는 일반적으로 use super::*;를 사용합니다.
#[test]는 비동기일 수 있나요?
기본적으로 #[test]는 비동기를 지원하지 않지만, 외부 crate(예: tokio 또는 async-std)를 사용하면 async #[test]를 만들 수 있습니다.
코드 예:
#[tokio::test] async fn test_async_add() { assert_eq!(add(2, 2).await, 4); }
테스트가 전역 상태를 변경할 수 있나요? 이를 피하는 방법은 무엇인가요?
러스트의 테스트는 기본적으로 병렬로 실행되므로, 전역 상태(static mut)를 동기화 없이 사용하면 경합 상태가 발생합니다.
모든 테스트가 같은 전역 변수를 사용합니다:
static mut COUNTER: u32 = 0; #[test] fn test_inc() { unsafe { COUNTER += 1; } }
장점:
단점:
각 테스트는 격리되어 있으며, 로컬 변수와 mock 객체를 사용합니다.
장점:
단점: