테스트 실행 방법 제어하기

테스트 실행 방법 제어하기

cargo run이 코드를 컴파일한 다음 결과 바이너리를 실행하는 것처럼, cargo test도 코드를 테스트 모드로 컴파일하고 결과 테스트 바이너리를 실행해요. cargo test가 만든 바이너리의 기본 동작은 모든 테스트를 병렬로 실행하고, 테스트 실행 중 생성되는 출력을 캡처해서 출력이 화면에 표시되지 않게 하고 테스트 결과와 관련된 출력을 더 쉽게 읽게 해주는 것이에요. 하지만 명령줄 옵션을 지정해 이 기본 동작을 바꿀 수 있어요.

출처: The Rust Book

몇몇 명령줄 옵션은 cargo test로 가고, 몇몇은 결과 테스트 바이너리로 가요. 이 두 종류의 인자를 구분하려면 cargo test로 가는 인자들을 나열한 다음 구분자 --를 쓰고, 그다음에 테스트 바이너리로 가는 인자들을 나열하면 돼요. cargo test --help를 실행하면 cargo test와 함께 사용할 수 있는 옵션들이 표시되고, cargo test -- --help를 실행하면 구분자 뒤에서 사용할 수 있는 옵션들이 표시돼요. 이 옵션들은 rustc 북의 "Tests" 섹션에도 문서화되어 있어요.

테스트를 병렬로 또는 순차로 실행하기

여러 테스트를 실행하면 기본적으로 스레드를 사용해 병렬로 실행되는데, 즉 더 빨리 끝나서 피드백을 더 빨리 얻을 수 있다는 뜻이에요. 테스트들이 동시에 실행되므로, 테스트들이 서로나 공유 상태(현재 작업 디렉토리나 환경 변수 같은 공유 환경을 포함해서)에 의존하지 않도록 해야 해요.

예를 들어 각 테스트가 test-output.txt라는 파일을 디스크에 만들고 그 파일에 데이터를 쓰는 코드를 실행한다고 해볼게요. 그런 다음 각 테스트가 그 파일의 데이터를 읽고, 파일이 특정 값, 각 테스트에서 다른 값을 포함하는지 주장해요. 테스트가 동시에 실행되므로, 한 테스트가 파일을 쓰고 읽는 사이에 다른 테스트가 파일을 덮어쓸 수도 있어요. 그러면 두 번째 테스트가 실패할 텐데, 코드가 잘못되어서가 아니라 테스트들이 병렬로 실행되면서 서로 간섭했기 때문이에요. 해결책 하나는 각 테스트가 다른 파일에 쓰도록 하는 것이고, 다른 해결책은 테스트를 한 번에 하나씩 실행하는 거예요.

테스트를 병렬로 실행하고 싶지 않거나 사용할 스레드 수를 더 세밀하게 제어하고 싶다면, --test-threads 플래그와 사용하고 싶은 스레드 수를 테스트 바이너리에 보내면 돼요. 다음 예시를 볼게요.

$ cargo test -- --test-threads=1

테스트 스레드 수를 1로 설정해서 프로그램이 어떤 병렬 처리도 사용하지 않도록 했어요. 하나의 스레드로 테스트를 실행하면 병렬로 실행하는 것보다 오래 걸리지만, 상태를 공유한다면 테스트들이 서로 간섭하지 않아요.

함수 출력 보여주기

기본적으로 테스트가 통과하면 Rust의 테스트 라이브러리는 표준 출력으로 출력된 것을 캡처해요. 예를 들어 테스트에서 println!을 호출하고 그 테스트가 통과하면, 터미널에서 println! 출력을 볼 수 없어요. 테스트가 통과했음을 나타내는 줄만 볼 수 있죠. 테스트가 실패하면 실패 메시지의 나머지와 함께 표준 출력으로 출력된 것을 볼 수 있어요.

예를 들어 리스팅 11-10에는 매개변수의 값을 출력하고 10을 반환하는 촐랑스러운 함수와, 통과하는 테스트 하나, 실패하는 테스트 하나가 있어요.

fn prints_and_returns_10(a: i32) -> i32 {
    println!("I got the value {a}");
    10
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn this_test_will_pass() {
        let value = prints_and_returns_10(4);
        assert_eq!(value, 10);
    }

    #[test]
    fn this_test_will_fail() {
        let value = prints_and_returns_10(8);
        assert_eq!(value, 5);
    }
}

cargo test로 이 테스트들을 실행하면 다음 출력을 보게 돼요.

$ cargo test
   Compiling silly-function v0.1.0 (file:///projects/silly-function)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.58s
     Running unittests src/lib.rs (target/debug/deps/silly_function-160869f38cff9166)

running 2 tests
test tests::this_test_will_fail ... FAILED
test tests::this_test_will_pass ... ok

failures:

---- tests::this_test_will_fail stdout ----
I got the value 8

thread 'tests::this_test_will_fail' panicked at src/lib.rs:19:9:
assertion `left == right` failed
  left: 10
 right: 5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

failures:
    tests::this_test_will_fail

test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

error: test failed, to rerun pass `--lib`

이 출력 어디에도 통과하는 테스트가 실행될 때 출력되는 I got the value 4가 보이지 않는다는 점을 주목하세요. 그 출력은 캡처됐어요. 실패한 테스트의 출력인 I got the value 8은 테스트 요약 출력의 섹션에 나타나는데, 이 섹션은 테스트 실패의 원인도 보여줘요.

통과하는 테스트의 출력된 값도 보고 싶다면, --show-output로 성공한 테스트의 출력도 보여주도록 Rust에게 말할 수 있어요.

$ cargo test -- --show-output

--show-output 플래그로 리스팅 11-10의 테스트를 다시 실행하면 다음 출력을 보게 돼요.

$ cargo test -- --show-output
   Compiling silly-function v0.1.0 (file:///projects/silly-function)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.60s
     Running unittests src/lib.rs (target/debug/deps/silly_function-160869f38cff9166)

running 2 tests
test tests::this_test_will_fail ... FAILED
test tests::this_test_will_pass ... ok

successes:

---- tests::this_test_will_pass stdout ----
I got the value 4

successes:
    tests::this_test_will_pass

failures:

---- tests::this_test_will_fail stdout ----
I got the value 8

thread 'tests::this_test_will_fail' panicked at src/lib.rs:19:9:
assertion `left == right` failed
  left: 10
 right: 5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

failures:
    tests::this_test_will_fail

test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

error: test failed, to rerun pass `--lib`

이름으로 테스트의 일부 실행하기

전체 테스트 모음을 실행하는 것은 때때로 오래 걸릴 수 있어요. 특정 영역의 코드를 작업 중이라면, 그 코드와 관련된 테스트만 실행하고 싶을 수 있어요. 실행하고 싶은 테스트의 이름이나 이름들을 cargo test에 인자로 넘겨 실행할 테스트를 선택할 수 있어요.

테스트의 일부를 실행하는 방법을 보여주기 위해, 먼저 리스팅 11-11에서처럼 add_two 함수에 대한 테스트를 세 개 만들고, 어떤 것을 실행할지 선택해 볼게요.

pub fn add_two(a: u64) -> u64 {
    a + 2
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn add_two_and_two() {
        let result = add_two(2);
        assert_eq!(result, 4);
    }

    #[test]
    fn add_three_and_two() {
        let result = add_two(3);
        assert_eq!(result, 5);
    }

    #[test]
    fn one_hundred() {
        let result = add_two(100);
        assert_eq!(result, 102);
    }
}

앞서 본 것처럼 인자 없이 테스트를 실행하면 모든 테스트가 병렬로 실행돼요.

$ cargo test
   Compiling adder v0.1.0 (file:///projects/adder)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.62s
     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)

running 3 tests
test tests::add_three_and_two ... ok
test tests::add_two_and_two ... ok
test tests::one_hundred ... ok

test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests adder

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

단일 테스트 실행하기

아무 테스트 함수의 이름을 cargo test에 넘겨 그 테스트만 실행할 수 있어요.

$ cargo test one_hundred
   Compiling adder v0.1.0 (file:///projects/adder)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.69s
     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)

running 1 test
test tests::one_hundred ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out; finished in 0.00s

one_hundred이라는 이름의 테스트만 실행됐고, 다른 두 테스트는 그 이름과 일치하지 않았어요. 테스트 출력은 끝에 2 filtered out을 표시해 실행되지 않은 테스트가 더 있었음을 알려줘요.

이런 식으로 여러 테스트의 이름을 지정할 수는 없어요. cargo test에 주어진 첫 번째 값만 사용되거든요. 하지만 여러 테스트를 실행하는 방법은 있어요.

여러 테스트를 실행하도록 필터링하기

테스트 이름의 일부를 지정할 수 있는데, 그 값과 이름이 일치하는 어떤 테스트든 실행돼요. 예를 들어 테스트 이름 두 개에 add가 들어 있으므로 cargo test add를 실행해 그 두 테스트를 돌릴 수 있어요.

$ cargo test add
   Compiling adder v0.1.0 (file:///projects/adder)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.61s
     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)

running 2 tests
test tests::add_three_and_two ... ok
test tests::add_two_and_two ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out; finished in 0.00s

이 명령은 이름에 add가 있는 모든 테스트를 실행하고 one_hundred이라는 테스트를 필터링했어요. 또한 테스트가 나타나는 모듈이 테스트 이름의 일부가 된다는 점도 주목하세요. 그래서 모듈 이름으로 필터링해 모듈의 모든 테스트를 실행할 수 있어요.

특별히 요청하지 않는 한 테스트 무시하기

때로는 특정 몇 테스트가 실행하는 데 아주 오래 걸릴 수 있어서, 대부분의 cargo test 실행에서 제외하고 싶을 수 있어요. 실행하고 싶은 모든 테스트를 인자로 나열하는 대신, ignore 어트리뷰트를 사용해 시간이 오래 걸리는 테스트를 어노테이션해 제외하면 돼요.

// Filename: src/lib.rs

pub fn add(left: u64, right: u64) -> u64 {
    left + right
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        let result = add(2, 2);
        assert_eq!(result, 4);
    }

    #[test]
    #[ignore]
    fn expensive_test() {
        // code that takes an hour to run
    }
}

#[test] 뒤에 제외하고 싶은 테스트에 #[ignore] 줄을 추가해요. 이제 테스트를 실행하면 it_works는 실행되지만 expensive_test는 실행되지 않아요.

$ cargo test
   Compiling adder v0.1.0 (file:///projects/adder)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.60s
     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)

running 2 tests
test tests::expensive_test ... ignored
test tests::it_works ... ok

test result: ok. 1 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests adder

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

expensive_test 함수는 ignored로 나열돼요. 무시된 테스트만 실행하고 싶다면 cargo test -- --ignored를 사용할 수 있어요.

$ cargo test -- --ignored
   Compiling adder v0.1.0 (file:///projects/adder)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.61s
     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)

running 1 test
test tests::expensive_test ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out; finished in 0.00s

   Doc-tests adder

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

어떤 테스트를 실행할지 제어함으로써 cargo test 결과가 빨리 반환되도록 할 수 있어요. ignored 테스트의 결과를 확인하는 것이 말이 되는 시점에 도달하고 결과를 기다릴 시간이 있으면, 대신 cargo test -- --ignored를 실행할 수 있어요. 무시됐든 아니든 모든 테스트를 실행하고 싶다면 cargo test -- --include-ignored로 실행할 수 있어요.

더 알아보기 (Learn more)