테스트 실행 방법 제어하기

cargo run 명령어가 코드를 컴파일하고 생성된 바이너리를 실행하는 것과 마찬가지로, cargo test 명령어는 코드를 테스트 모드에서 컴파일하고 생성된 바이너리를 실행합니다. cargo test에 의해 생성된 바이너리의 기본 동작은 모든 테스트를 병렬로 실행하고 테스트가 수행되는 동안 발생된 출력을 캡처하는 것으로, 출력이 표시되는 것을 막고 테스트 결과와 관련된 출력을 읽기 편하게 해 줍니다. 하지만 커맨드 라인 옵션을 지정하여 이러한 기본 동작을 변경할 수 있습니다.

명령어 옵션은 cargo test에 전달되는 것도 있고, 테스트 바이너리에 전달되는 것도 있습니다. 이 둘을 구분하기 위해 cargo test에 전달할 인수를 먼저 나열하고, -- 구분자 (separator) 를 쓰고, 그 뒤에 테스트 바이너리에게 전달할 인수를 나열합니다. cargo test --help 명령어는 cargo test 명령어에 사용 가능한 옵션을 표시하고, cargo test -- --help 명령어는 구분자 이후에 사용 가능한 옵션을 표시합니다.

테스트를 병렬 혹은 순차적으로 실행하기

여러 테스트를 실행할 때는 기본적으로 스레드를 사용해 병렬 실행되는데, 이는 테스트를 더 빨리 끝내서 피드백을 더 빠르게 얻기 위함입니다. 여러 테스트가 동시에 실행되므로, 각 테스트가 공유 상태(공유 자원, 현재 작업 디렉터리, 환경 변수 등)를 갖거나 다른 테스트에 의존해서는 안 됩니다.

예시를 생각해 보죠. 각 테스트가 test-output.txt 파일을 생성하고 그 파일에 어떤 데이터를 작성하는 코드를 실행하도록 만들었습니다. 각 테스트는 파일의 데이터를 읽고, 파일이 특정 값을 포함하고 있는지 확인하며, 특정 값은 테스트마다 다릅니다. 여러 테스트가 동시에 실행되므로, 어떤 테스트가 파일에 작성하고 읽는 사이에 다른 테스트가 파일의 내용을 덮어쓸 수도 있습니다. 이 경우 방해받은 테스트는 실패할 겁니다. 코드에 문제가 있어서가 아니라, 병렬 실행되는 도중 방해받아서 말이죠. 한 가지 해결책은 각 테스트가 서로 다른 파일에 작성하도록 만드는 것일 테고, 다른 해결책은 테스트를 한 번에 하나씩 실행하는 것입니다.

테스트를 병렬로 실행하고 싶지 않거나, 사용할 스레드의 개수에 대한 미세 조정이 필요한 경우에는 --test-threads 플래그와 함께 테스트 바이너리에서 사용할 스레드 개수를 지정할 수 있습니다. 다음과 같이 사용합니다.

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

스레드 개수를 1로 설정하여 프로그램이 어떠한 병렬 처리도 사용하지 않도록 하였습니다. 스레드 하나만 사용해 테스트를 실행하면 병렬 실행에 비해 더 느려지겠지만, 서로 상태를 공유하는 테스트가 방해받을 일이 사라집니다.

함수 출력 표시하기

기본적으로, 러스트 테스트 라이브러리는 성공한 테스트의 모든 표준 출력 (standard output) 을 캡처합니다. 테스트에서 println! 매크로를 호출해도, 해당 테스트가 성공하면 터미널에서 println!의 출력을 찾아볼 수 없습니다. 해당 테스트가 성공했다고 표시된 줄만 볼 수 있죠. 테스트가 실패하면 표준 출력으로 출력됐던 모든 내용이 실패 메시지 아래에 표시됩니다.

예제 11-10은 매개변수를 출력하고 10을 반환하는 단순한 함수와, 성공하는 테스트와 실패하는 테스트를 작성한 예시입니다.

파일명: src/lib.rs

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!(10, value);
    }

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

예제 11-10: println!을 호출하는 함수 테스트

cargo test 명령어를 실행하면 다음 결과가 나타납니다.

$ cargo test
   Compiling silly-function v0.1.0 (file:///projects/silly-function)
    Finished test [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 'assertion failed: `(left == right)`
  left: `5`,
 right: `10`', src/lib.rs:19:9
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 옵션을 전달하여 성공한 테스트의 출력도 표시하도록 할 수 있습니다.

$ cargo test -- --show-output

예제 11-10의 테스트를 --show-output 플래그로 실행한 결과는 다음과 같습니다.

$ cargo test -- --show-output
   Compiling silly-function v0.1.0 (file:///projects/silly-function)
    Finished test [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 'assertion failed: `(left == right)`
  left: `5`,
 right: `10`', src/lib.rs:19:9
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 함수에 대한 세 가지 테스트를 작성하고 하나만 골라 실행해 보겠습니다.

파일명: src/lib.rs

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

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

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

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

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

예제 11-11: 세 가지 서로 다른 이름의 테스트

앞서 살펴본 것처럼, 테스트를 아무 인수도 없이 실행하면 모든 테스트가 병렬로 실행됩니다.

$ cargo test
   Compiling adder v0.1.0 (file:///projects/adder)
    Finished test [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 [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 명령어는 첫 번째 값만 사용합니다. 하지만 여러 테스트를 실행하는 방법이 없지는 않습니다.

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

테스트 이름의 일부만 지정하면 해당 값에 맞는 모든 테스트가 실행됩니다. 예를 들어, cargo test add 명령어를 실행하면 우리가 작성한 세 개의 테스트 중 add가 포함된 두 개가 실행됩니다.

$ cargo test add
   Compiling adder v0.1.0 (file:///projects/adder)
    Finished test [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 속성을 어노테이션하면 됩니다.

파일명: src/lib.rs

#[test]
fn it_works() {
    assert_eq!(2 + 2, 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 [unoptimized + debuginfo] target(s) in 0.60s
     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)

running 2 tests
test expensive_test ... ignored
test 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 [unoptimized + debuginfo] target(s) in 0.61s
     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)

running 1 test
test 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 결과는 빨리 확인할 수 있습니다. 무시한 테스트의 결과를 확인해야 할 때가 되었고 그 결과를 기다릴 시간이 있다면, cargo test -- --ignored 명령어를 실행합니다. 무시되었건 말건 간에 모든 테스트를 실행하고 싶다면 cargo test -- --include-ignored를 실행할 수 있습니다.