테스트 실행 제어
cargo test와 테스트 바이너리의 인수 경계를 구분하고, 실행 범위·출력·동시성을 조절해 실패를 재현합니다.
cargo test는 테스트 모드로 코드를 컴파일한 뒤 테스트 실행 파일을 실행합니다. 일반 단위 테스트와 통합 테스트 대상은 각각 별도의 실행 파일이 되며, Cargo는 이 실행 파일들을 차례로 실행합니다. 각 실행 파일 안에서는 테스트 하네스가 기본적으로 테스트 함수를 병렬 실행하고 표준 출력과 표준 오류를 캡처합니다.
문서 테스트는 별도 경계입니다. rustdoc는 현재 각 문서 코드 블록을 별도 프로세스로 만들어 병렬 실행하지만, Cargo 문서는 이 실행 모델이 보장되지 않으며 바뀔 수 있다고 명시합니다. 따라서 일반 테스트 실행 파일의 직렬 실행이나 하네스 내부 스레드 수 설명을 doctest 전체의 고정 스케줄 계약으로 확장하면 안 됩니다.
명령줄의 -- 앞은 Cargo가 해석하고, 뒤는 테스트 실행 파일의 하네스가 해석합니다. Cargo 구문에는 선택적인 [TESTNAME] 자리가 하나 있으며, 이 값은 테스트 실행 파일에 전달되어 전체 테스트 경로에 대한 부분 문자열 필터로 쓰입니다.
cargo test --help는 Cargo 옵션을 표시하고, cargo test -- --help는 테스트 하네스 옵션을 표시합니다.
Rust · Cargo / libtest · semantic table
cargo test 인수는 -- 경계에서 해석 주체가 갈린다
--는 옵션 자체가 아니라 전달 경계입니다. 앞에서는 Cargo가 패키지와 대상을 고르고, 선택적인 [TESTNAME] 하나를 테스트 실행 파일에 넘깁니다. 뒤에서는 테스트 하네스가 범위, 출력, 동시성 옵션을 해석합니다.
| 명령줄 위치 | 대표 구문 | 해석 주체 | 실행 의미 |
|---|---|---|---|
-- 앞 선택 옵션 |
-p app, --lib, --release |
Cargo | 패키지, 테스트 대상, 빌드 프로필을 고릅니다. |
[TESTNAME] |
cargo test one_hundred |
Cargo가 한 위치만 받고 하네스에 전달 | 하네스가 전체 테스트 경로의 부분 문자열로 일치시킵니다. 정확히 하나를 뜻하지 않습니다. |
| 전달 경계 | -- |
Cargo | 이 뒤의 인수를 해석하지 않고 테스트 실행 파일에 전달합니다. |
-- 뒤 실행 옵션 |
--exact, --show-output, --test-threads=1 |
테스트 하네스 | 이름 일치, 캡처 출력 표시, 하네스 작업 스레드 수를 조절합니다. |
-- 뒤 무시 옵션 |
--ignored, --include-ignored |
테스트 하네스 | 컴파일된 #[ignore] 테스트만 실행하거나 일반 테스트와 함께 실행합니다. |
Cargo selection
-- 앞 선택 옵션
-p app, --lib, --release는 Cargo가 패키지, 테스트 대상, 빌드 프로필을 고르는 데 사용합니다.
one positional slot
[TESTNAME]
Cargo가 한 위치만 받아 테스트 실행 파일에 전달합니다. 하네스는 전체 테스트 경로의 부분 문자열로 일치시키므로 정확히 하나를 뜻하지 않습니다.
routing boundary
--
Cargo는 이 뒤의 인수를 해석하지 않고 테스트 실행 파일로 전달합니다.
harness behavior
범위, 출력, 동시성
--exact, --show-output, --test-threads=1은 테스트 하네스가 해석합니다.
ignored tests
무시된 테스트 선택
#[ignore] 테스트도 컴파일됩니다. --ignored는 이 테스트만, --include-ignored는 일반 테스트와 함께 실행합니다.
도움말도 같은 경계를 따릅니다. cargo test --help는 Cargo 도움말을, cargo test -- --help는 현재 테스트 하네스 도움말을 표시합니다. 명령 전체는 토큰 사이에서 줄바꿈할 수 있고 각 옵션 토큰은 한 덩어리로 읽습니다.
테스트를 병렬 혹은 순차적으로 실행하기
하나의 테스트 실행 파일 안에서 여러 테스트를 실행할 때 하네스는 기본적으로 스레드를 사용해 병렬 실행합니다. 이는 테스트를 더 빨리 끝내서 피드백을 더 빠르게 얻기 위함입니다.
여러 테스트가 동시에 실행되므로, 각 테스트가 공유 상태(공유 자원, 현재 작업 디렉터리, 환경 변수 등)를 갖거나 다른 테스트에 의존해서는 안 됩니다.
예시를 생각해 보죠.
각 테스트가 test-output.txt 파일을 생성하고 그 파일에 어떤 데이터를 작성하는 코드를 실행하도록 만들었습니다.
각 테스트는 파일의 데이터를 읽고, 파일이 특정 값을 포함하고 있는지 확인하며, 특정 값은 테스트마다 다릅니다.
여러 테스트가 동시에 실행되므로, 어떤 테스트가 파일에 작성하고 읽는 사이에 다른 테스트가 파일의 내용을 덮어쓸 수도 있습니다.
이 경우 방해받은 테스트는 실패할 겁니다.
코드에 문제가 있어서가 아니라, 병렬 실행되는 도중 방해받아서 말이죠.
한 가지 해결책은 각 테스트가 서로 다른 파일에 작성하도록 만드는 것일 테고, 다른 해결책은 테스트를 한 번에 하나씩 실행하는 것입니다.
테스트 하네스의 작업 스레드 수를 조정하려면 --test-threads 옵션에 개수를 지정합니다.
다음과 같이 사용합니다.
$ cargo test -- --test-threads=1이 설정은 해당 테스트 실행 파일의 하네스가 테스트 함수를 한 번에 하나씩 시작하게 합니다. 테스트 코드가 자체적으로 만드는 스레드나 프로세스, Cargo의 빌드 작업, 외부 시스템의 동시 접근까지 막지는 않습니다.
따라서 공유 상태는 테스트마다 격리하는 것이 우선입니다. --test-threads=1은 하네스 수준의 동시성이 실패 원인인지 확인하는 진단 수단으로 사용하며, 공유 자원 간섭이 사라진다고 단정해서는 안 됩니다.
함수 출력 표시하기
기본적으로 러스트 테스트 하네스는 테스트의 표준 출력과 표준 오류를 캡처합니다.
테스트에서 println! 매크로를 호출해도, 해당 테스트가 성공하면 터미널에서 println!의 출력을 찾아볼 수 없습니다.
해당 테스트가 성공했다고 표시된 줄만 볼 수 있죠.
테스트가 실패하면 캡처한 출력이 실패 정보와 함께 표시됩니다.
예제 10-10은 매개변수를 출력하고 10을 반환하는 단순한 함수와, 성공하는 테스트와 실패하는 테스트를 작성한 예시입니다.
예제 10-10:println!을 호출하는
함수 테스트
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);
}
}
cargo test 명령어를 실행하면 다음과 같은 결과를 볼 수 있습니다. 테스트 표시 순서, 경로와 바이너리 해시, 시간, 프로필 및 패닉 문구는 도구 체인과 실행 환경에 따라 달라질 수 있습니다.
$ cargo test
running 2 tests
test tests::this_test_will_pass ... ok
test tests::this_test_will_fail ... FAILED
---- tests::this_test_will_fail stdout ----
I got the value 8
test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out성공한 테스트에서 출력했던 I got the value 4는 캡처되었으므로 기본 요약에서는 찾아볼 수 없습니다.
실패한 테스트에서 출력한 I got the value 8은 테스트 실패 정보와 함께 나타납니다.
성공한 테스트의 캡처된 출력도 보고 싶다면 테스트 하네스에 --show-output 옵션을 전달합니다. 이 옵션은 성공한 테스트의 캡처된 출력을 테스트가 끝난 뒤 표시하며, 출력을 실시간으로 흘려보내는 캡처 해제 옵션은 아닙니다.
$ cargo test -- --show-output예제 10-10의 테스트를 --show-output 옵션으로 실행하면 성공한 테스트의 캡처된 출력도 결과 뒤에 표시됩니다.
$ cargo test -- --show-output
running 2 tests
test tests::this_test_will_pass ... ok
test tests::this_test_will_fail ... FAILED
---- tests::this_test_will_pass stdout ----
I got the value 4
---- tests::this_test_will_fail stdout ----
I got the value 8
test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out테스트가 실패했을 때는 실행 범위, 출력, 동시성 중 어느 축을 조정할지 먼저 정하면 원인을 빠르게 좁힐 수 있습니다.
이름을 지정해 일부 테스트만 실행하기
간혹 테스트 모음을 전부 실행하는 데 시간이 오래 걸리기도 합니다.
코드의 특정한 부분에 대한 작업 중이라면 해당 부분의 코드에 관련된 테스트만 실행하고 싶을 수도 있습니다.
cargo test 명령어에 테스트의 이름을 인수로 넘겨 어떤 테스트를 실행할지 선택할 수 있습니다.
일부 테스트만 실행하는 법을 알아보기 위해, 먼저 예제 10-11처럼
add_two 함수에 대한 세 가지 테스트를 작성하고 하나만 골라 실행해보겠습니다.
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));
}
}
앞서 살펴본 것처럼, 테스트를 아무 인수도 없이 실행하면 각 테스트 실행 파일 안에서 선택된 테스트가 기본적으로 병렬 실행됩니다.
$ 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의 [TESTNAME] 자리에 문자열을 하나 전달하면 libtest가 전체 테스트 경로에서 부분 문자열로 일치하는 테스트를 고릅니다. 다음 예제에서는 우연히 하나만 일치하지만, 이 명령 자체가 정확히 한 테스트를 보장하는 것은 아닙니다.
$ 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가 해석하는 -- 앞에는 [TESTNAME] 자리가 하나뿐입니다. 따라서 cargo test add one_hundred처럼 위치 인수를 하나 더 쓰면 첫 값만 사용하는 것이 아니라 명령줄 오류가 납니다.
정확히 하나의 테스트를 고르려면 전체 테스트 경로와 --exact를 함께 사용합니다.
$ cargo test tests::one_hundred -- --exact-- 뒤 인수는 테스트 실행 파일로 전달됩니다. 현재 Cargo 파서가 이 인수들을 전달하는 방식과 rustc 테스트 하네스가 여러 위치 필터를 받는 방식을 함께 보면, 다음 명령은 add 또는 one_hundred과 부분 일치하는 테스트를 고릅니다. 이 OR 동작은 Cargo 명령 설명 하나만의 보장이 아니라 Cargo 소스와 현재 rustc 하네스 문서를 결합한 해석이므로, 도구 체인을 바꿀 때는 cargo test -- --help로 확인하세요.
$ cargo test -- add one_hundred테스트를 필터링하여 여러 테스트 실행하기
테스트 이름의 일부만 지정하면 해당 값에 맞는 모든 테스트가 실행됩니다.
예를 들어, 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] 속성을 붙이면 됩니다.
#[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를 사용합니다.
테스트 실행 범위 조절 기준
테스트 실패를 좁힐 때는 실행 범위, 캡처된 출력, 하네스 동시성을 서로 독립된 축으로 보고 필요한 옵션만 조합합니다.
Rust · test diagnostics · flowchart
실패를 보면 범위, 출력, 동시성을 독립적으로 조절한다
먼저 기본 실행으로 증거를 남긴 뒤 필요한 축만 바꿉니다. 이름과 ignore는 실행 범위, --show-output은 캡처된 출력, --test-threads=1은 해당 하네스의 작업 스레드 수만 조절합니다.
baseline
1. 기본 실행으로 현상 확인
먼저 cargo test 결과를 남깁니다. 테스트 순서, 경로, 바이너리 해시, 시간과 출력 문구는 도구 체인과 환경에 따라 달라질 수 있습니다.
choose one axis
2. 지금 무엇을 확인할까?
실행 범위, 캡처된 출력, 하네스 동시성은 독립된 축입니다. 증거에 필요한 축부터 고릅니다.
range
3. 실행 범위 좁히기
부분 문자열 필터와 --exact로 이름 범위를 조절합니다. 컴파일된 무시 테스트는 --ignored 또는 --include-ignored로 고릅니다.
output
4. 캡처된 출력 함께 보기
--show-output은 성공한 테스트의 캡처된 표준 출력과 표준 오류를 종료 뒤에 표시합니다. 실시간 캡처 해제가 아닙니다.
concurrency
5. 하네스 동시성 제한
--test-threads=1은 해당 실행 파일의 하네스 작업 스레드만 제한합니다. 테스트 자체의 스레드나 외부 경쟁까지 제거하지 않습니다.
fan-in
6. 필요한 옵션을 조합해 재실행
바꾼 축과 관찰 결과를 기록하고, 필요하면 독립 옵션을 조합해 같은 실패가 재현되는지 비교합니다.
여러 일반 테스트 대상은 별도 실행 파일로 Cargo가 차례로 실행하고, 각 실행 파일 안에서는 테스트 하네스가 기본적으로 병렬 실행합니다. 직렬 진단은 공유 상태 격리를 대신하지 않습니다.