테스트 실행 방식 제어하기
cargo run 이 코드를 컴파일한 뒤 실행 파일을 실행하듯이, cargo test 는 코드를
테스트 모드로 컴파일한 뒤 생성된 테스트 바이너리를 실행합니다. cargo test 가
만든 바이너리의 기본 동작은 모든 테스트를 병렬로 실행하고, 테스트 실행 중 발생한
출력을 캡처하는 것입니다. 이렇게 하면 출력이 화면에 그대로 섞여 나오지 않아 테스트
결과 관련 출력이 더 읽기 쉬워집니다. 물론 이 기본 동작은 명령줄 옵션으로 바꿀 수
있습니다.
어떤 옵션은 cargo test 자체에 전달되고, 어떤 옵션은 최종적으로 실행되는 테스트
바이너리에 전달됩니다. 이 둘을 구분하려면 먼저 cargo test 용 인수를 적고,
구분자 -- 를 쓴 뒤 테스트 바이너리용 인수를 적습니다. cargo test --help 를
실행하면 cargo test 에 사용할 수 있는 옵션이 나오고, cargo test -- --help
를 실행하면 구분자 뒤에 사용할 수 있는 옵션이 나옵니다. 이 옵션들은 The rustc
Book_ 의 “Tests” 절에도 문서화되어 있습니다.
테스트를 병렬 또는 순차적으로 실행하기
여러 테스트를 실행할 때, 기본적으로는 스레드를 사용해 병렬로 실행됩니다. 그래서 더 빨리 끝나고 피드백도 더 빨리 받을 수 있습니다. 다만 테스트가 동시에 실행되므로, 테스트끼리 서로에게 의존하지 않고, 현재 작업 디렉터리나 환경 변수처럼 공유된 상태에도 의존하지 않도록 주의해야 합니다.
예를 들어 모든 테스트가 디스크에 test-output.txt 라는 파일을 만들고 그 안에 데이터를 기록한다고 해 봅시다. 그리고 각 테스트가 그 파일 내용을 다시 읽어 특정 값을 가지고 있는지 검사한다고 합시다. 이때 각 테스트가 기대하는 값은 서로 다릅니다. 테스트가 동시에 실행되면, 한 테스트가 파일을 쓰고 읽는 사이에 다른 테스트가 그 파일을 덮어써 버릴 수 있습니다. 그러면 두 번째 테스트는 코드가 잘못되어서가 아니라, 병렬 실행 중 테스트끼리 서로 간섭했기 때문에 실패합니다. 한 가지 해결책은 각 테스트가 서로 다른 파일을 쓰게 만드는 것이고, 또 다른 해결책은 테스트를 한 번에 하나씩 실행하는 것입니다.
테스트를 병렬로 실행하고 싶지 않거나, 사용할 스레드 수를 더 세밀하게 제어하고 싶다면
--test-threads 플래그와 원하는 스레드 수를 테스트 바이너리에 전달하면 됩니다.
예를 들면 다음과 같습니다.
$ cargo test -- --test-threads=1
여기서는 테스트 스레드 수를 1 로 설정했기 때문에, 프로그램에게 병렬성을 사용하지
말라고 지시한 것입니다. 이렇게 하면 병렬 실행보다 시간이 더 걸릴 수 있지만, 공유 상태가
있는 테스트들이 서로 간섭하지는 않습니다.
함수 출력 보기
기본적으로 테스트가 통과하면, 러스트 테스트 라이브러리는 표준 출력에 찍힌 내용을
캡처합니다. 예를 들어 테스트 안에서 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);
}
}
println! 을 호출하는 함수에 대한 테스트이 테스트들을 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 을 사용해 러스트에게 성공한
테스트의 출력도 보여 달라고 할 수 있습니다.
$ cargo test -- --show-output
목록 11-10의 테스트를 다시 --show-output 과 함께 실행하면, 다음과 같은 출력이
보입니다.
$ 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 속성을 붙여 아예 제외할
수 있습니다. 다음 예를 보세요.
파일명: 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 결과를 더 빨리 받아 볼 수
있습니다. 무시된 테스트 결과까지 볼 시간이 있을 때는 cargo test -- --ignored
를 실행하면 되고, 무시 여부와 상관없이 전부 실행하고 싶다면
cargo test -- --include-ignored 를 사용하면 됩니다.