DaleSchool

내 코드가 맞는지 확인하기: 테스트

중급15분

학습 목표

  • #[test]로 단위 테스트를 작성할 수 있다
  • assert 계열 매크로를 활용할 수 있다
  • cargo test로 테스트를 실행하고 결과를 해석할 수 있다
  • 통합 테스트 디렉터리 구조를 이해한다

동작하는 코드

테스트는 라이브러리 코드 바로 옆에 두는 것이 가장 빠른 피드백을 줍니다. #[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;으로 불러오면 됩니다.

직접 해보기

  1. src/lib.rsfn greet(name: &str) -> String 함수를 만들고, "안녕, {name}" 문자열을 돌려주는 테스트를 작성하세요.
  2. 고의로 실패하는 테스트를 하나 만들어 cargo test 출력이 어떻게 달라지는지 확인해 보세요.
  3. tests/ 폴더를 만들고, 라이브러리를 사용하는 짧은 통합 테스트를 작성해 프로젝트 전반이 기대대로 동작하는지 검증해 보세요.
  1. panic! 대신 assert! 사용 — 아래 코드는 실패 시 직접 panic!을 호출합니다. assert!/assert_eq!로 바꾸고 메시지를 보강해 보세요.
  2. Result 반환 테스트fn parse_user_id(input: &str) -> Result<u32, String> 함수를 가정하고, 성공/실패 케이스 두 개의 테스트를 작성해 보세요.
  3. 통합 테스트 분리 — 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를 사용할 수 없다