본문 바로가기

테스트 조직화

소스 내부의 cfg test 유닛 테스트와 tests 디렉터리의 통합 테스트를 구분해 공개·비공개 동작을 적절한 경계에서 검증합니다.

이번 장의 시작 부분에서 언급했듯, 테스트는 복잡한 분야입니다.

사람들은 저마다 다른 용어와 구조를 사용합니다.

러스트 커뮤니티는 테스트를 크게 유닛 테스트(unit test, 단위 테스트라고도 함), 통합 테스트(integration test) 두 종류로 나눕니다.

유닛 테스트는 작고 더 집중적입니다.

한 번에 하나의 모듈만 테스트하며, 모듈의 비공개 인터페이스도 테스트할 수 있습니다.

통합 테스트는 완전히 라이브러리 외부에 위치하며, 따라서 여러분이 작성한 라이브러리를 외부 코드에서 사용할 때와 똑같은 방식을 사용합니다.

하나의 테스트에서 잠재적으로 여러 모듈이 사용되기도 합니다.

라이브러리의 각 부분을 따로 사용할 때와 여러 부분을 함께 사용할 때의 동작을 확인하려면 두 종류의 테스트를 작성합니다. 이 확인은 선택한 입력과 시나리오에 대한 것이며, 모든 경우의 작동을 보장하지는 않습니다.

유닛·통합·문서 테스트의 컴파일 경계

ARCHITECTURE · TEST TARGET BOUNDARIES

유닛·통합·문서 테스트의 컴파일 경계

테스트의 크기가 아니라 컴파일 대상과 접근 경계가 종류를 가른다. #[cfg(test)] 모듈은 소스 곁에서 비공개 구현까지 보고, tests/*.rs와 문서 테스트는 외부 계약을 검증한다.

유닛·통합·문서·바이너리 테스트의 접근 경계유닛 모듈은 라이브러리의 비공개 구현에도 접근한다. 통합과 문서 테스트는 공개 API를 호출한다. 바이너리 동작 테스트는 별도 프로세스로 실행 파일을 검증한다. 유닛 모듈에서 main으로 이어지는 실행 관계는 없다.패키지 소스 · src/외부 테스트 경계비공개 포함공개 API공개 API프로세스라이브러리 구현src/lib.rs공개 · 비공개 아이템소스 곁 유닛 테스트#[cfg(test)] mod tests자식 모듈의 접근 규칙얇은 실행 진입점src/main.rs로직은 lib.rs 재사용통합 테스트 대상tests/*.rs각 대상은 별도 크레이트문서 테스트rustdoc 예제문서 속 공개 계약바이너리 동작 테스트CARGO_BIN_EXE_<name>별도 자식 프로세스로 실행비공개 구현: 유닛 · 공개 계약: 통합/문서 · 실행 동작: 프로세스cargo test --test <target> → 통합 대상 선택cargo test -p <package> → 패키지 선택
테스트의 접근 경계와 실제 검증 대상6노드4검증참조관계이다. 유닛테스트는자식모듈에서라이브러리비공개구현에도접근한다. 통합테스트와문서테스트는서로별도외부경계에서라이브러리공개API를호출하며각화살표는lib구현노드로들어간다. 바이너리동작테스트는별도프로세스로main실행진입점을검증한다. 유닛→통합→문서→바이너리의실행순서나유닛→main관계는없다. lib사용두외부간선은별도바깥레일로유닛노드를거치지않고lib하단의다른포트로도착한다.src · 라이브러리와 유닛외부 테스트 경계src · 실행 진입점라이브러리 구현src/lib.rs공개·비공개 아이템소스 곁 유닛 테스트#[cfg(test)] tests자식 모듈 접근통합 테스트 대상tests/*.rs별도 크레이트공개 API 호출문서 테스트rustdoc 예제공개 API 호출바이너리 동작 검사CARGO_BIN_EXE_<name> 경로얇은 실행 진입점src/main.rs별도 프로세스로 검사화살표: 접근·검증 대상유닛 → main 실행 간선 없음
  1. 유닛 테스트

    src/lib.rs의 자식 tests 모듈은 테스트 설정에서 포함되며 조상 모듈의 비공개 아이템도 검증합니다.

  2. 통합 테스트

    각 tests/*.rs 파일은 별도 크레이트로 컴파일되어 라이브러리의 공개 API를 외부 사용자처럼 호출합니다.

  3. 문서 테스트

    rustdoc 예제는 별도 문서 테스트 대상이며 유닛·통합 테스트와 같은 크기 분류가 아니라 다른 컴파일 경계입니다.

  4. 바이너리 경계

    재사용 로직은 src/lib.rs로 옮기고 src/main.rs는 얇게 유지합니다. 바이너리 자체는 자식 프로세스로도 검증할 수 있습니다.

  5. 선택 실행

    cargo test --test <target>은 한 통합 대상을, cargo test -p <package>은 작업공간의 한 패키지를 고릅니다. 출력 순서는 안정적인 API로 가정하지 않습니다.

유닛 테스트의 개별 함수는 보통 병렬로 실행되고 Cargo는 별도 통합 테스트 대상을 현재 직렬로 실행하지만, 실행·출력 순서는 계약이 아니다.


유닛 테스트

유닛 테스트의 목적은 각 코드 단위를 나머지 코드와 분리하여, 제대로 작동하지 않는 코드가 어느 부분인지 빠르게 파악하는 것입니다.

유닛 테스트는 src 디렉터리 내의 각 파일에 테스트 대상이 될 코드와 함께 작성합니다.

각 파일에 tests 모듈을 만들고 cfg(test)를 어노테이션하는 게 일반적인 관례입니다.

테스트 모듈과 #[cfg(test)]

#[cfg(test)]는 해당 모듈을 rustc --test 같은 테스트 빌드 설정에서만 포함합니다. Cargo가 일반 라이브러리를 빌드할 때는 제외하고 유닛 테스트 대상으로 빌드할 때 포함하며, 실제 실행 여부는 대상·필터와 --no-run 같은 옵션에 따라 달라집니다.

라이브러리 빌드 시 테스트 코드는 제외되므로, 컴파일 소요 시간이 짧아지고, 컴파일 결과물 크기도 줄어듭니다.

이후에 알게 되겠지만, 통합 테스트는 별도의 디렉터리에 위치하기 때문에 #[cfg(test)] 어노테이션이 필요 없습니다.

하지만 유닛 테스트는 일반 코드와 같은 파일에 위치하기 때문에, #[cfg(test)] 어노테이션을 작성해 컴파일 결과물에 포함되지 않도록 명시해야 합니다.

이번 장 첫 번째 절에서 adder 프로젝트를 생성했을 때 Cargo가 생성했던 코드를 다시 살펴봅시다.

src/lib.rs
#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        let result = 2 + 2;
        assert_eq!(result, 4);
    }
}

이 코드는 자동으로 생성된 테스트 모듈입니다.

cfg 속성은 설정(configuration)을 의미하며, 러스트는 이 아이템을 특정 설정 옵션 적용 시에만 포함합니다.

이 경우 옵션 값은 러스트에서 테스트를 컴파일, 실행하기 위해 제공하는 test입니다.

#[cfg(test)]는 명령어 이름이 아니라 테스트 컴파일 설정을 기준으로 코드를 포함합니다.

여기에는 #[test]로 어노테이션된 함수뿐만 아니라 모듈 내 도우미 함수도 포함됩니다.

비공개 함수 테스트하기

비공개 함수도 직접 테스트해야 하는지에 대해서는 많은 논쟁이 있습니다.

다른 언어에서는 비공개 함수를 테스트하기 어렵거나, 불가능하게 만들어 두었습니다.

여러분의 테스트 철학이 어떤지는 모르겠지만, 러스트의 비공개 규칙은 비공개 함수를 테스트하도록 허용합니다.

예제 10-12는 비공개 함수 internal_adder를 보여줍니다.

예제 10-12: 비공개 함수 테스트하기
src/lib.rs
pub fn add_two(a: i32) -> i32 {
    internal_adder(a, 2)
}

fn internal_adder(a: i32, b: i32) -> i32 {
    a + b
}

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

    #[test]
    fn internal() {
        assert_eq!(4, internal_adder(2, 2));
    }
}

internal_adder 함수는 pub으로 표시되지 않았습니다.

테스트는 그냥 러스트 코드이며 tests 모듈도 그저 또 다른 모듈일 뿐입니다.

‘경로를 사용하여 모듈 트리의 아이템 참조하기’ 절에서 논의한 바와 같이, 자식 모듈 내의 아이템은 자기 조상 모듈에 있는 아이템을 사용할 수 있습니다.

이 테스트에서는 use super::*를 사용하여 tests 모듈의 부모에 있는 아이템을 모두 스코프 안으로 가져오고 있고, 따라서 테스트가 internal_adder를 호출할 수 있습니다.

혹시 여러분이 비공개 함수를 테스트해서는 안 된다는 주의라면, 러스트가 이를 강요하지는 않습니다.


통합 테스트

통합 테스트는 여러분이 만든 라이브러리와 완전히 분리되어 있습니다.

통합 테스트는 외부 코드와 마찬가지로, 여러분이 만든 라이브러리의 공개 API만 호출 가능합니다.

통합 테스트의 목적은 라이브러리의 여러 부분을 함께 사용했을 때 제대로 작동하는지 확인하는 것입니다.

각각 따로 사용했을 때 잘 작동하는 코드도 함께 사용할 때는 문제가 발생할 수 있기 때문에 통합 테스트도 중요합니다.

통합 테스트를 작성하려면 먼저 tests 디렉터리를 만들어야 합니다.

tests 디렉터리

프로젝트 디렉터리 최상위, 다시 말해 src 옆에 tests 디렉터리를 생성합니다.

Cargo는 디렉터리 내 통합 테스트 파일을 자동으로 인식합니다.

그런 다음에는 원하는 만큼 통합 테스트 파일을 만들 수 있고, Cargo는 각 파일을 개별 크레이트로 컴파일합니다.

통합 테스트를 직접 만들어 봅시다.

예제 10-12 코드를 src/lib.rs에 작성한 채로 tests 디렉터리를 만들고, tests/integration_test.rs 파일을 생성합니다.

디렉터리 구조는 다음과 같이 보일 것입니다.

Cargo.lock
Cargo.toml
lib.rs
integration_test.rs

tests/integration_test.rs 파일에 예제 10-13의 코드를 입력합시다.

예제 10-13: adder 크레이트 내 함수를 테스트하는 통합 테스트
tests/integration_test.rs
use adder;

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

tests 디렉터리의 각 대상은 별개의 크레이트이며, Cargo는 패키지의 라이브러리를 각 통합 대상에 연결합니다. 통합 대상에서 접근할 수 있는 것은 라이브러리의 공개 아이템입니다.

위 예제는 코드 최상단에 use adder;를 명시했지만, Rust 2018 이후 에디션에서 Cargo가 연결한 라이브러리 이름은 extern prelude에 있으므로 이 줄 없이도 adder::add_two(2)라는 경로를 사용할 수 있습니다. 함수를 이름만으로 호출하고 싶다면 use adder::add_two;로 가져옵니다. use는 이름을 가져오는 구문이며, 비공개 함수를 공개하거나 라이브러리 의존성을 설치하는 구문은 아닙니다.

tests/integration_test.rs 내 코드는 #[cfg(test)]가 필요 없습니다.

Cargo는 기본 자동 탐색에서 이 파일을 통합 테스트 대상으로 인식합니다. 일반적인 cargo build에는 포함되지 않지만, cargo test 외에도 cargo check --tests로 테스트 대상을 검사하거나 cargo build --tests로 실행 파일을 빌드할 수 있습니다. 이 두 명령은 테스트 함수를 실행하지 않습니다. 대상에 포함하는 것과 단언을 실제로 실행하는 것을 구별합니다.

cargo test를 다시 실행시켜 보죠.

$ cargo test
   Compiling adder v0.1.0 (file:///projects/adder)
    Finished test [unoptimized + debuginfo] target(s) in 1.31s
     Running unittests src/lib.rs (target/debug/deps/adder-1082c4b063a8fbe6)

running 1 test
test tests::internal ... ok

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

     Running tests/integration_test.rs (target/debug/deps/integration_test-1082c4b063a8fbe6)

running 1 test
test it_adds_two ... ok

test result: ok. 1 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는 테스트 실행 파일 하나가 실패하면 다음 실행 파일로 진행하지 않습니다. --no-fail-fast를 사용하면 실패 뒤에도 다른 테스트 대상들을 실행합니다.

예를 들어 위 기본 명령에서 유닛 테스트 실행 파일이 실패하면 뒤의 통합·문서 테스트 결과가 생기지 않을 수 있습니다. 같은 하네스 안의 선택된 테스트는 일반적으로 끝까지 실행하며, 이 Cargo 옵션은 실행 파일 사이의 진행을 제어합니다.

첫 번째 절인 유닛 테스트는 앞에서 본 것과 같습니다.

유닛 테스트가 한 줄씩(internal은 예제 10-12에서 추가했었습니다) 출력되고, 유닛 테스트 결과 요약 줄이 출력됩니다.

통합 테스트 절은 Running tests/integration_test.rs 줄로 시작합니다.

그다음 통합 테스트 내 각각의 테스트 함수가 한 줄씩 출력되고, 통합 테스트 결과 요약은 Doc-tests adder 절이 시작하기 직전에 출력됩니다.

각각의 통합 테스트 파일은 별도의 출력 절을 생성하므로, tests 디렉터리에 파일을 추가하면 통합 테스트 절이 더 만들어질 것입니다.

통합 테스트도 마찬가지로 cargo test 명령어에 테스트 함수명을 인수로 전달해 특정 통합 테스트 함수를 실행할 수 있습니다.

특정 통합 테스트 파일의 모든 테스트를 실행하려면, cargo test 명령어에 --test 인수로 파일명을 전달하면 됩니다.

$ cargo test --test integration_test
   Compiling adder v0.1.0 (file:///projects/adder)
    Finished test [unoptimized + debuginfo] target(s) in 0.64s
     Running tests/integration_test.rs (target/debug/deps/integration_test-82e7799c1bc62298)

running 1 test
test it_adds_two ... ok

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

이 명령어는 tests/integration_test.rs 파일 내의 테스트만 실행합니다.

통합 테스트 내 서브 모듈

통합 테스트를 추가하다 보면, 조직화를 위해 tests 디렉터리에 더 많은 파일이 필요할 수도 있습니다; 예를 들어, 테스트 함수가 테스트하는 기능별로 그룹화할 수도 있죠.

앞서 말했듯, tests 내 각 파일은 각각의 크레이트로 컴파일되는데, 이는 각 통합 테스트 파일이 각각의 크레이트로 취급된다는 점 때문에 여러분이 만든 크레이트를 사용할 실제 사용자처럼 분리된 스코프를 만들어 내는 데에는 유용합니다.

통합 테스트에서도 하위 모듈을 선언해 코드를 공유할 수 있습니다. 다만 tests 최상위의 Rust 파일은 기본 자동 탐색에서 별도 대상이 되므로, 공용 헬퍼를 그 위치에 두면 의도하지 않은 테스트 실행 파일까지 만들어집니다.

여러 통합 테스트 파일에서 유용하게 사용할 도우미 함수 묶음을 6장 ‘별개의 파일로 모듈 분리하기’ 절의 과정대로 공통 모듈로 분리하려 할 때, tests 디렉터리 파일의 동작 방식은 걸림돌이 됩니다.

예를 들어 tests/common.rs 파일을 생성하고, 여러 테스트 파일의 테스트 함수에서 호출하려는 setup 함수를 작성한다고 가정해봅시다.

tests/common.rs
pub fn setup() {
    // 여기에 라이브러리 테스트와 관련된 설정 코드를 작성하려고 합니다
}

이제 테스트를 실행하면, 결과 출력에 새로운 절이 common.rs 파일 때문에 생성된 모습을 볼 수 있습니다.

common.rs 파일은 어떤 테스트 함수도 담고 있지 않고, 다른 곳에서 setup 함수를 호출하지도 않았는데 말이죠.

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

running 1 test
test tests::internal ... ok

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

     Running tests/common.rs (target/debug/deps/common-92948b65e88960b4)

running 0 tests

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

     Running tests/integration_test.rs (target/debug/deps/integration_test-92948b65e88960b4)

running 1 test
test it_adds_two ... ok

test result: ok. 1 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

원하던 것은 다른 통합 테스트 파일에서 사용할 코드를 공유하는 것이지, 테스트 출력 결과에 common과 running 0 tests가 출력되는 게 아니었죠.

아래 다이어그램은 통합 테스트 보조 코드를 파일 크레이트가 아니라 공유 모듈로 배치해야 하는 이유를 비교한 것입니다.

통합 테스트 파일과 공용 헬퍼의 디렉터리 구조

TREE · TEST DIRECTORY

통합 테스트 파일과 공용 헬퍼의 디렉터리 구조

tests 바로 아래의 Rust 파일은 기본 자동 탐색에서 각각 통합 테스트 대상이 된다. 공용 설정 코드는 tests/common/mod.rs에 두고 각 대상이 mod common;으로 가져온다.

통합 테스트 대상과 공용 모듈의 파일 트리src와 tests는 나란하다. 기본 자동 탐색에서 foo.rs와 bar.rs는 별도 대상이고 common/mod.rs는 각 대상이 mod common으로 선언하는 모듈이다.패키지 루트adder/라이브러리 소스src/lib.rs통합 테스트 루트tests/별도 targetfoo.rs별도 targetbar.rs공용 modulecommon/setup()mod.rsfoo.rs와 bar.rs: mod common; → common::setup()cargo test --test foo → foo.rs 대상 선택
별도 테스트 대상과 공용 모듈의 포함 관계패키지루트의src/lib.rs와tests/는형제다. tests/아래foo.rs와bar.rs는각별도자동탐색대상이며common/아래mod.rs는공용모듈이다. 7노드6계층관계이고 선은디렉터리포함을뜻하며실행순서가아니다. foo.rs와bar.rs각파일에서mod common;을선언하고setup을호출한다. 세형제자식이세로배치되어도foo→bar→common실행연쇄로연결하지않는다.패키지 루트adder/라이브러리 소스src/lib.rs통합 테스트 루트tests/별도 targetfoo.rs별도 targetbar.rs공용 modulecommon/mod.rssetup() 정의선: 디렉터리 포함 관계각 대상은 mod common; 선언공용 파일 ≠ 독립 실행 대상
  1. 프로젝트 루트

    src와 tests는 나란히 있으며 Cargo는 tests의 최상위 Rust 파일을 통합 대상으로 찾습니다.

  2. 별도 대상

    tests/foo.rs와 tests/bar.rs는 각각 독립된 테스트 크레이트로 컴파일됩니다.

  3. 공용 모듈

    tests/common/mod.rs는 최상위 대상이 아니라 가져다 쓰는 모듈입니다.

  4. 명시적 연결

    각 테스트 파일에 mod common;을 쓰고 common::setup()을 호출합니다.

  5. 선택 실행

    cargo test --test foo는 foo.rs 대상만 선택합니다.

common/mod.rs는 유용한 관례다. 공유 모듈 배치는 가능하지만, 최상위 common.rs를 두면 기본 자동 탐색에서 또 하나의 통합 테스트 대상으로 발견된다.

테스트 출력 결과에서 common을 제외하려면 tests/common.rs 파일 대신 tests/common/mod.rs 파일을 생성해야 합니다.

프로젝트 디렉터리는 이제 아래와 같은 모양이 됩니다.

Cargo.lock
Cargo.toml
lib.rs
mod.rs
integration_test.rs

이는 6장의 ‘대체 파일 경로’ 절에서 언급했던 러스트에서 사용 가능한 예전 명명 규칙입니다.

이러한 방식의 파일명 규칙을 따르는 파일은 통합 테스트 파일로 취급하지 않습니다.

setup 함수를 tests/common/mod.rs 파일로 옮기고 tests/common.rs 파일을 삭제하면 더 이상 테스트 결과 출력에 common이 나타나지 않습니다.

이 예제의 tests/common/mod.rs는 각 대상이 가져오는 모듈이므로 독립 테스트 대상으로 자동 발견되지 않습니다. 모든 하위 파일이 그렇다는 뜻은 아닙니다. Cargo는 tests/<name>/main.rs 형태도 기본 통합 테스트 대상으로 자동 탐색할 수 있고, 매니페스트에서 대상을 명시하거나 자동 탐색을 끌 수도 있습니다.

tests/common/mod.rs 파일을 생성하고 나면 다른 통합 테스트 파일에서 모듈처럼 사용할 수 있습니다.

다음은 tests/integration_test.rs 파일 내 it_adds_two 테스트에서 setup 함수를 호출하는 예시입니다.

tests/integration_test.rs
use adder;

mod common;

#[test]
fn it_adds_two() {
    common::setup();
    assert_eq!(4, adder::add_two(2));
}

예제 6-21에서 배운 모듈 선언대로 mod common;를 선언했습니다.

선언하고 나면 common::setup() 함수를 호출할 수 있습니다.

바이너리 크레이트에서의 통합 테스트

src/lib.rs 파일이 없고 src/main.rs 파일만 있는 바이너리 크레이트라면, tests 디렉터리에 통합 테스트를 만들어서 src/main.rs 파일에 정의된 함수를 use 구문으로 가져올 수 없습니다.

다른 크레이트에서 사용할 수 있도록 함수를 노출하는 건 라이브러리 크레이트뿐입니다.

바이너리 크레이트는 자체적으로 실행되게 되어있습니다.

바이너리를 제공하는 러스트 프로젝트들이 src/main.rs 파일은 간단하게 작성하고, 로직은 src/lib.rs 파일에 위치시키는 이유 중 하나가 이 때문입니다.

이런 구조로 작성하면 중요 기능을 통합 테스트에서 use 구문으로 가져와 테스트할 수 있습니다.

라이브러리 테스트는 실행 파일의 인수 처리·입출력·종료 상태까지 보장하지 않습니다. 이 동작이 계약이라면 통합 테스트에서 CARGO_BIN_EXE_<name>로 실행 파일 경로를 찾아 자식 프로세스로 검증할 수 있습니다.


테스트 요약

러스트의 테스트 기능을 사용하면 선택한 입력과 단언으로 기대 동작을 명시하고, 코드를 변경한 뒤에도 검사한 결과가 유지되는지 확인할 수 있습니다.

유닛 테스트는 비공개 세부 구현을 포함한 라이브러리의 각 부분이 별도로 잘 작동하는지 확인합니다.

통합 테스트는 외부 코드가 라이브러리를 사용하는 것과 동일한 방식으로 라이브러리 공개 API를 이용하여 라이브러리의 여러 부분이 함께 사용될 때 제대로 작동하는지 확인합니다.

러스트의 타입 시스템과 소유권 규칙이 일부 버그를 방지해 주긴 하지만, 여러분이 작성한 코드가 의도대로 작동하지 않는 논리 버그를 제거하려면 테스트도 마찬가지로 중요합니다.

이번 장과 이전 장에서 배운 지식을 결합하여 프로젝트를 진행해봅시다!


단위 테스트와 통합 테스트 배치

러스트 테스트는 모듈 안의 단위 테스트와 tests 디렉터리의 통합 테스트가 서로 다른 관점에서 코드를 검증합니다.


테스트 조직화 학습 판단 기준표

테스트 위치를 고를 때는 먼저 접근해야 할 아이템과 실행 경계를 확인합니다. 비공개 구현을 직접 비교하는 테스트는 소스의 자식 모듈에, 외부 사용자처럼 공개 API를 호출하는 테스트는 통합 대상에 둘 수 있습니다. 명령줄 인수·표준 출력·종료 상태를 검사하려면 라이브러리 호출만으로 끝내지 않고 실행 파일의 프로세스 경계를 검사합니다.

예시 기본 자동 탐색을 쓰는 새 adder 패키지에 src/lib.rs의 유닛 테스트 하나, tests/api.rs의 테스트 두 개, tests/io.rs의 테스트 하나가 있다고 합시다. tests/common/mod.rs에는 setup 함수만 있고 두 통합 대상이 각각 mod common;을 선언합니다. setup은 두 대상 안에 모듈로 컴파일되며 그 파일 하나 때문에 별도 테스트 대상이 추가되지는 않습니다. 각 대상은 별도 실행 파일이므로 공용 파일 배치가 모든 대상의 메모리 상태를 한 곳에 합치는 것은 아닙니다. Cargo의 통합 테스트 대상 설명은 별도 크레이트와 공용 모듈을 구별합니다.

독립 연습: 이 배치에서 cargo test --test api는 어느 파일의 몇 개 테스트 함수를 실행하나요? cargo test --lib는 무엇을 실행하나요? tests/api.rs에서 원 비공개 internal_adder를 직접 호출하도록 바꾸면 접근할 수 있나요? cargo test --no-run이 성공했을 때 함수의 단언도 검사되었다고 결론 내릴 수 있는지 설명하세요. 여기에는 무시·이름 필터나 사용자 지정 하네스가 없습니다.

컴파일 대상과 실행 결과를 나눠 확인하기

--test api는 tests/api.rs 통합 실행 파일의 테스트 두 개를 실행하고, --lib는 라이브러리의 유닛 테스트 하나를 실행합니다. 통합 대상은 외부 크레이트이므로 internal_adder가 공개되지 않은 채라면 직접 호출할 수 없습니다. 소스의 자식 유닛 모듈에서 검사하거나 공개 add_two를 통해 외부 계약을 검사합니다. --no-run은 컴파일만 하므로 단언은 실행되지 않습니다. 컴파일 성공과 테스트 실행 성공은 별도의 근거입니다.