동작하는 코드
테스트는 라이브러리 코드 바로 옆에 두는 것이 가장 빠른 피드백을 줍니다. #[cfg(test)] 모듈 안에 #[test] 함수들을 모아두면 릴리스 빌드에는 포함되지 않아요.
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
pub fn divide(a: i32, b: i32) -> Result<i32, String> {
if b == 0 {
return Err("0으로 나눌 수 없어요".into());
}
Ok(a / b)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn add_works() {
assert_eq!(add(2, 3), 5);
assert_ne!(add(-1, 1), 0);
}
#[test]
fn divide_returns_error() -> Result<(), String> {
assert_eq!(divide(10, 2)?, 5);
assert!(divide(5, 0).is_err());
Ok(())
}
}
assert_eq!,assert_ne!,assert!는 실패 시 어떤 값이 달랐는지 친절히 보여줘요.- 테스트 함수가
Result<(), E>를 반환하면?연산자를 사용할 수 있어서 에러 처리 코드가 깔끔해집니다.
cargo test 살펴보기
터미널에서 cargo test를 실행하면 모든 테스트를 자동으로 찾아 실행해 줍니다.
$ cargo test
Compiling mylib v0.1.0 (/Users/dale/mylib)
Finished test [unoptimized + debuginfo] target(s) in 1.15s
Running unittests src/lib.rs (target/debug/deps/mylib-abc123)
test tests::add_works ... ok
test tests::divide_returns_error ... ok
failures:
tests::add_works
---- tests::add_works stdout ----
thread 'tests::add_works' panicked at 'assertion failed: `(left == right)`
left: `6`,
right: `5`', src/lib.rs:8:9
ok/FAILED가 단번에 보이도록 정렬돼요.- 실패한 테스트는 스택 트레이스와 함께 마지막에 다시 요약되기 때문에 원인 파악이 쉽습니다.
- 특정 테스트만 실행하고 싶다면
cargo test add_works처럼 이름을 전달하세요.
통합 테스트 구조
라이브러리 외부에서의 사용법을 검증하려면 tests/ 디렉터리를 활용하세요. 이 폴더 안의 각 파일은 완전히 독립된 바이너리로 빌드돼 실제 사용 시나리오를 테스트합니다.
my-project/
├─ src/
│ └─ lib.rs
├─ tests/
│ ├─ cli.rs
│ └─ api.rs
└─ Cargo.toml
tests/cli.rs 예시:
use assert_cmd::Command;
#[test]
fn prints_help() {
let mut cmd = Command::cargo_bin("todo")
.expect("binary todo not found");
cmd.arg("--help").assert().success();
}
- 통합 테스트에서는 크레이트의 공개 API만 접근할 수 있어요. 공개하지 않은 내부 함수는 단위 테스트로 검증하세요.
- 보조 모듈이나 헬퍼가 필요하면
tests/common/mod.rs에 작성하고 각 테스트 파일에서mod common;으로 불러오면 됩니다.
직접 해보기
src/lib.rs에fn greet(name: &str) -> String함수를 만들고, "안녕, {name}" 문자열을 돌려주는 테스트를 작성하세요.- 고의로 실패하는 테스트를 하나 만들어
cargo test출력이 어떻게 달라지는지 확인해 보세요. tests/폴더를 만들고, 라이브러리를 사용하는 짧은 통합 테스트를 작성해 프로젝트 전반이 기대대로 동작하는지 검증해 보세요.
- panic! 대신 assert! 사용 — 아래 코드는 실패 시 직접
panic!을 호출합니다.assert!/assert_eq!로 바꾸고 메시지를 보강해 보세요. - Result 반환 테스트 —
fn parse_user_id(input: &str) -> Result<u32, String>함수를 가정하고, 성공/실패 케이스 두 개의 테스트를 작성해 보세요. - 통합 테스트 분리 — CLI 프로그램이 있다고 가정하고,
tests/cli.rs에서Command::cargo_bin으로 실행 후 stdout을 검증하는 테스트를 작성해 보세요.
Q1. Rust에서 테스트 함수로 인식되려면 어떻게 해야 할까요?
- A) 함수 이름이
test_로 시작해야 한다 - B) 함수 위에
#[test]속성을 붙여야 한다 - C)
tests모듈 안에 있어야 한다 - D)
cfg(test)매크로를 사용해야 한다
Q2. 다음 중 Result<(), E>를 반환하는 테스트의 장점은?
- A) 테스트가 더 빨라진다
- B)
?연산자를 사용해 에러 전파가 간결해진다 - C) assert 매크로를 사용할 수 없다
- D) cargo test가 에러 메시지를 숨긴다
Q3. 통합 테스트(tests/ 폴더)의 특징으로 옳은 것은?
- A) 크레이트의 비공개 함수에도 접근할 수 있다
- B) 각 파일이 독립 바이너리로 빌드된다
- C)
cargo test --lib를 실행해야만 포함된다 - D) 통합 테스트에서는
use를 사용할 수 없다