Rust는 안정성과 신뢰성을 강조하는 언어답게 테스트를 매우 중요하게 여깁니다. 표준 라이브러리에는 테스트를 쉽게 작성하고 실행할 수 있도록 test 프레임워크가 기본 내장되어 있으며, cargo test 명령어로 손쉽게 실행할 수 있습니다. 오늘은 단위 테스트, 통합 테스트, 그리고 테스트 관련 어노테이션 및 모범 사례에 대해 알아보겠습니다.
1. 기본적인 단위 테스트 구조
Rust의 테스트는 일반적으로 소스 파일 내에 다음과 같은 구조로 작성됩니다.
// 실제 함수
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
// 테스트 모듈
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() {
assert_eq!(add(2, 3), 5);
}
}
- #[cfg(test)]: 이 모듈은 테스트 시에만 컴파일되도록 지정합니다.
- mod tests { … }: 테스트용 하위 모듈을 의미합니다. 실제 코드와 분리된 테스트 코드입니다.
- use super::*;: 상위 모듈(super)의 add 함수를 불러오는 것입니다. 이 줄을 주석처리하면 “add함수가 없다”고 합니다.
- #[test]: 테스트 함수임을 표시합니다.
- test_add 함수를 만들어서 add(2, 3)의 결과와 5가 같은지 비교해서 맞으면 ok, 다르면 failed가 발생합니다.
assert_eq!: 기대값과 실제값이 같은지 검사합니다.
가. 실행 방법
cargo test
cargo는 프로젝트 전체를 빌드하고 #[test] 함수들을 자동으로 실행합니다.
나. lib.rs 파일 작성 및 실행
(1) assert_eq!의 결과 두 값이 같은 경우
src 폴더 아래에 lib.rs란 파일로 작성했습니다.

day21 폴더에서 cargo test를 실행하니(VS Code를 보면 함수 위에 Run Test가 있으므로 이걸 눌러도 됨)
컴파일되고 실행되는데 먼저 unittests src\lib.rs라고 표시되어 lib.rs의 test annotation을 먼저 실행하고, 그 다음은 main.rs를 실행합니다.
lib.rs의 테스트 결과는
test tests::test_add … ok라고 표시되고,
그 아래 test result: ok. 1 passed; 0 failed… 등 내용이 표시되는데,
main.rs의 테스트 결과는 test 어노테이션이 없어서 0으로 표시됩니다.
그리고, assert_eq! 구문만 실행되고, main.rs에 있는 println!(“Hello, world!”);문은 실행되지 않습니다. lib.rs에 println!문이 있어도 실행되지 않습니다.
cargo test –lib라고 하면 lib.rs의 test annotation만 실행하고, main.rs에 test 어노테이션이 있는지 체크하지 않습니다.
(2) assert_eq!의 결과 두 값이 다른 경우

add(2, 3)의 결과는 5인데, 6과 비교하면 두 값이 다르므로 FAILED라고 결과가 표시되고,
그 아래를 보면 thread ‘tests::test_add’ panicked at day21\src\lib.rs:12:9:라고 panic이 발생했고,
왼쪽은 5, 오른쪽은 6으로 “왼쪽과 오른쪽이 같다는 단언(주장)이 실패했다”고 설명합니다.
2. 다양한 assert 매크로
Rust에서는 다음과 같은 다양한 테스트 도구를 제공합니다.
매크로 | 설명 |
---|---|
assert! | 조건이 true인지 확인 |
assert_eq! | 두 값이 같은지 확인 |
assert_ne! | 두 값이 다른지 확인(not equal) |
dbg! | 디버깅 용도로 값을 출력 |
panic! | 강제로 실패시키기 |
예제:
#[test]
fn test_assertions() {
let a = 10;
let b = 20;
assert!(a < b);
assert_eq!(a + b, 30);
assert_ne!(a, b);
}
위 코드를 cargo test –lib로 실행하면
모든 결과가 True이므로 test result: ok인데, 세 번이 아니라 한 번만 표시되고,

assert!(a > b);
assert_eq!(a + b, 40);
으로 수정해서 2개를 False로 만든 후 실행하면
a > b에서 panic이 되기 때문에 a + b와 40을 비교하는 부분은 가지도 못하고 끝납니다.

따라서, a < b로 수정하고 실행하면 a + b = 40에서 실패가 발생합니다.
3. 실패하는 테스트
테스트가 실패했을 경우에는 어떤 테스트가 어떤 이유로 실패했는지 친절히 알려줍니다. 예를 들어:
#[test]
fn test_fail() {
assert_eq!(1 + 1, 3); // 실패
}
실행 시 다음과 같은 메시지가 출력됩니다.
“왼쪽은 2인데, 오른쪽은 3으로 왼쪽과 오른쪽이 같지 않다”고 합니다.
---- test_fail stdout ----
thread 'test_fail' panicked at day21\src\lib.rs:27:5:
assertion `left == right` failed
left: 2
right: 3
4. 테스트 함수에서 panic이 발생하면?
Rust에서는 테스트 함수가 panic을 일으키면 테스트가 실패한 것으로 간주됩니다. 일부러 panic을 유발하는 테스트는 다음처럼 작성할 수 있습니다:
#[test]
#[should_panic]
fn test_panic() {
panic!("강제로 실패시킴");
}
- #[should_panic]: 이테스트는 panic이 발생해야 성공으로 간주됩니다.
![테스트 함수에서 panic이 발생하면 :
#[should_panic]: panic이 발생해야 ok](https://overmt.com/wp-content/uploads/2025/07/test6.webp)
5. 결과 반환형을 이용한 테스트
테스트 함수가 Result<(), E>를 반환할 수도 있습니다.
#[test]
fn test_with_result() -> Result<(), String> {
if 2 + 2 == 4 {
Ok(())
} else {
Err(String::from("'2 + 2는 4'가 아닙니다!"))
}
}
가. Ok(성공)인 경우
successes:
test_with_result
라고 test_with_result가 성공임을 표시합니다.

나. 에러인 경우
failures에 Error: “‘2 + 2는 4’가 아닙니다!”라고 표시됩니다.

이 방식은 ? 연산자를 활용할 수 있어 더 깔끔하게 작성할 수 있습니다.
다. ? 연산자를 이용한 구문
fn check_math() -> Result<(), String> {
if 2 + 2 == 4 {
Ok(())
} else {
Err(String::from("'2 + 2는 4'가 아닙니다!"))
}
}
#[test]
fn test_with_result() -> Result<(), String> {
check_math()?; // 실패하면 즉시 반환됨
Ok(())
}
6. 테스트 분류
가. 단위 테스트 (Unit Test)
- 하나의 함수나 모듈의 기능을 검사
- 일반적으로 동일한 파일 내 mod tests 안에 작성
나. 통합 테스트 (Integration Test)
- 실제 사용 시나리오처럼 여러 모듈이 함께 작동하는지를 테스트
- tests/ 디렉터리 하위에 별도의 .rs 파일로 작성
예:
my_project/
├── src/
│ └── lib.rs
└── tests/
└── integration_test.rs
// src/lib.rs
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
// tests/integration_test.rs
use my_project::add;
#[test]
fn test_add_integration() {
assert_eq!(add(10, 20), 30);
}
위 integration_test.rs의 use문에 my_project가 있는데, day21 등으로 프로젝트명에 맞게 수정해야 합니다.
cargo test를 실행하면 src폴더의 lib.rs와 main.rs뿐만 아니라, tests폴더의 integration_test.rs까지 test가 있는지 찾아서 test를 진행하고, day21 폴더의 Doc-tests도 진행합니다.

그러나, tests 폴더의 integration_test.rs에서 Run Test를 누르면
integration_test.rs의 test만 실행합니다.

7. #[ignore]와 특정 테스트만 실행하기
테스트가 너무 오래 걸리거나 특별한 조건에서만 실행하고 싶을 때는 #[ignore] 어노테이션을 붙일 수 있습니다.
#[test]
#[ignore]
fn long_running_test() {
// 오래 걸리는 테스트
}
cargo test 실행 시 무시되며,
test long_running_test … ignored
![#[ignore] - 테스트시 제외](https://overmt.com/wp-content/uploads/2025/07/image-8.png)
아래와 같이 실행하면 #[ignore]
가 붙은 테스트만 실행합니다.
cargo test -- --ignored
위 코드를 실행하면 1 passed; … 1 filtered out;이라고 표시되는데, long_running_test는 통과되고, test_add_integration은 제외되었다는 뜻입니다.
![#[ignore]가 붙은 테스트만 실행](https://overmt.com/wp-content/uploads/2025/07/image-9.png)
특정 테스트만 실행하려면 이름을 지정합니다:
cargo test test_add
8. 테스트 모범 사례
- 작고 독립적인 테스트를 작성하세요.
- 각 테스트는 부작용이 없어야 합니다 (예: 파일 시스템 접근, DB 쓰기 등).
- 테스트 함수 이름은 명확하게 지어야 합니다(test_add, test_divide_by_zero 등).
- 테스트는 문서화 주석과 함께 유지하면 가독성이 높아집니다.
9. 문서화 테스트 (doctest)
Rust는 문서 주석(///
) 안에 포함된 코드도 테스트로 실행합니다.
/// 두 수를 더합니다.
///
/// # 예시
/// ```
/// let result = my_crate::add(2, 3);
/// assert_eq!(result, 5);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
cargo test를 실행하면 이 문서 예제도 자동으로 테스트합니다.
my_crate는 프로젝트명에 맞게 수정해야 하는데, day21 프로젝트이므로 day21로 수정했습니다.
위에서는 Doc-tests시 test 개수가 0이었는데, 여기는 1로 표시되고, ‘add (line 48) … ok’라고 표시됩니다.

10. 마무리
Rust의 테스트 프레임워크는 매우 강력하면서도 간단하게 사용할 수 있습니다. 실수를 미리 잡아내고 코드를 문서화하며 안정성을 높이기 위해 테스트는 필수적인 도구입니다.