소개
테스트는 예상 동작을 실행 가능한 증거로 바꿉니다. 목적이 분명한 테스트는 하나의 동작을 호출하고, 실제 결과를 예상 결과와 비교하며, 변경 사항으로 인한 회귀를 자동으로 감지해 이후의 수정 작업을 더 안전하게 만듭니다.
준비된 배송 견적 라이브러리에 테스트를 추가합니다. 실제 동작을 구현한 함수는 의도적으로 간단하게 작성되어 있으므로, Rust 테스트의 위치, 어설션으로 예상 결과를 표현하는 방법, 어떤 경계 사례가 중요한지, Cargo로 한 번에 하나의 테스트 대상을 실행하는 방법에 집중할 수 있습니다.
목적이 분명한 단위 테스트 추가
이 단계에서는 라이브러리 코드 옆에 단위 테스트를 작성하고, 이름으로 지정한 테스트만 실행합니다.
프로젝트 경로는 /home/labex/project/shipping-quote입니다. 해당 디렉터리로 이동합니다.
cd /home/labex/project/shipping-quote
편집하기 전에 짧은 라이브러리 소스 코드를 확인합니다. 여기서 sed -n '1,220p' 명령은 읽기 전용으로 파일을 확인할 때만 사용합니다. -n은 자동 출력을 억제하고, 1,220p는 1행부터 220행까지 명시적으로 출력합니다. 이 범위면 편집기를 열지 않고도 전체 파일을 확인할 수 있습니다.
sed -n '1,220p' src/lib.rs
#[...] 형식으로 작성하는 구문은 **attribute(어트리뷰트)**입니다. 바로 아래에 있는 Rust 항목에 메타데이터를 연결합니다. #[cfg(test)] 어트리뷰트는 다음 테스트 모듈을 테스트 빌드에서만 컴파일하도록 Rust에 지시합니다. 모듈은 관련 항목을 묶어 이름을 붙인 컨테이너입니다. 다음 Guided Lab에서는 모듈을 일반적인 코드 구성 도구로 다룹니다.
mod tests 안에서 경로에 사용하는 super는 부모 모듈을 의미하고, *는 부모 모듈에서 접근할 수 있는 모든 이름을 의미합니다. 따라서 use super::*;를 사용하면 테스트 모듈 안에서 라이브러리 함수를 짧은 이름으로 사용할 수 있습니다. 마지막으로 #[test]가 표시된 함수는 Cargo가 찾아 실행할 수 있는 테스트가 됩니다.
Nano로 소스 파일을 엽니다.
nano src/lib.rs
// FIRST_UNIT_TEST를 다음 테스트로 바꿉니다.
#[test]
fn light_parcel_costs_five() {
assert_eq!(shipping_cost(3), 5);
}
assert_eq!는 두 표현식의 값을 비교합니다. 두 값이 같으면 테스트가 계속되고, 다르면 테스트가 실패하면서 두 값을 출력합니다. 여기서 이 테스트는 3킬로그램 소포의 배송비가 5단위라는 규칙을 기록합니다.
Ctrl+O를 눌러 저장하고 Enter를 누른 다음, Ctrl+X를 눌러 종료합니다. cargo test 뒤에 테스트 이름을 입력하면 이름에 해당 텍스트가 포함된 테스트만 실행할 수 있습니다.
cargo test light_parcel_costs_five
Cargo가 테스트 빌드를 컴파일하고 이름이 일치하는 단위 테스트를 보고합니다.
test tests::light_parcel_costs_five ... ok
test result: ok. 1 passed; 0 failed; ...
tests:: 접두사는 이 테스트가 라이브러리 내부의 로컬 테스트 모듈에 있다는 뜻이고, ok는 어설션이 통과했음을 나타냅니다.
경계 사례 다루기
이 단계에서는 배송 동작이 바뀌는 경계에서 테스트를 추가합니다.
3과 같은 대표 값은 가벼운 소포 범위 안의 한 지점을 검증하지만, 버그는 경계에서 자주 발생합니다. 준비된 match에서 1..=5는 1부터 5까지의 모든 값과 일치하는 **범위 패턴(range pattern)**이며, 양 끝값인 1과 5도 포함합니다. 이 구문은 반복문에서 사용하는 포함 범위 표기법을 새로운 패턴 문맥에서 재사용한 것입니다. 이 함수는 0에서 특별한 동작을 하고, 정확히 5킬로그램까지 5단위 요금을 유지합니다. 이러한 값을 테스트하면 두 경계를 모두 기록할 수 있습니다.
라이브러리 파일을 다시 엽니다.
nano src/lib.rs
// EDGE_UNIT_TESTS를 다음 두 테스트로 바꿉니다.
#[test]
fn empty_parcel_is_free() {
assert_eq!(shipping_cost(0), 0);
}
#[test]
fn five_kilograms_is_still_light() {
assert_eq!(shipping_cost(5), 5);
}
각 테스트는 이름에 하나의 규칙을 설명하고, 목적이 분명한 어설션 하나를 포함합니다. 따라서 테스트가 실패했을 때 원인을 쉽게 찾을 수 있습니다. 저장한 다음 Nano를 종료합니다.
tests:: 필터는 로컬 단위 테스트 모듈에 있는 테스트의 전체 이름과 일치합니다. 이 필터를 사용하면 별도의 통합 테스트 대상을 일단 제외하고 세 개의 단위 테스트를 모두 실행할 수 있습니다.
cargo test tests::
세 개의 단위 테스트가 통과했다는 결과가 확인되어야 합니다.
test tests::empty_parcel_is_free ... ok
test tests::five_kilograms_is_still_light ... ok
test tests::light_parcel_costs_five ... ok
test result: ok. 3 passed; 0 failed; ...
이 예제들은 일반적인 값 하나와 가벼운 소포 규칙에서 중요한 두 경계값을 함께 검증합니다.
통합 테스트 완성
이 단계에서는 별도의 파일에서 라이브러리의 공개 인터페이스를 통해 라이브러리를 테스트합니다.
src/lib.rs 안의 단위 테스트는 구현 코드와 가까이 있으며 부모 모듈의 내부 항목을 사용할 수 있습니다. 통합 테스트는 최상위 tests/ 디렉터리에 두고, 별도의 테스트 프로그램으로 컴파일되며, 외부 호출자처럼 라이브러리의 공개 인터페이스만 사용합니다. 패키지 이름 shipping-quote는 소스 코드에서 밑줄을 사용한 Rust 크레이트 이름 shipping_quote가 됩니다. 다음 Lab에서는 패키지, 크레이트, 모듈, 공개 가시성이라는 용어를 정리합니다.
준비된 통합 테스트 뼈대를 확인합니다.
sed -n '1,160p' tests/order_workflow.rs
이 파일은 공개된 order_total 함수만 가져옵니다. 40단위 주문과 3킬로그램 소포를 인수로 함수를 호출한 뒤, 하나의 어설션을 작성할 자리만 남겨 둔 상태입니다. 파일을 엽니다.
nano tests/order_workflow.rs
// INTEGRATION_ASSERTION으로 끝나는 자리 표시자 어설션 줄을 다음 코드로 바꿉니다.
assert_eq!(total, 45);
예상 총액은 40단위의 소계와 가벼운 소포 배송비 5단위를 합한 값입니다. 저장한 다음 Nano를 종료합니다.
--test order_workflow 옵션은 tests/order_workflow.rs 파일에 해당하는 통합 테스트 대상을 선택합니다.
cargo test --test order_workflow
이 대상은 공개 워크플로 테스트 결과를 보고합니다.
test order_total_includes_shipping ... ok
test result: ok. 1 passed; 0 failed; ...
마지막으로 필터 없이 전체 패키지 테스트 모음을 실행합니다.
cargo test
Cargo는 세 개의 단위 테스트와 통합 테스트를 실행합니다. 서로 다른 테스트 바이너리이므로 결과 요약이 여러 개 표시되는 것은 정상입니다. 이름이 표시된 모든 테스트에 ok가 나오고, 모든 요약에서 실패 수가 0인지 확인합니다.
요약
로컬 단위 테스트를 추가하고, 규칙의 경계를 검증했으며, 라이브러리의 공개 API를 사용하는 통합 테스트를 완성했습니다. 또한 Cargo 필터를 사용해 테스트 하나, 모듈 하나, 통합 테스트 대상 하나 또는 전체 테스트 모음을 실행했습니다. 이러한 습관은 개발 중 빠르게 동작을 확인하고, 코드 인계 전에 더 폭넓게 회귀를 방지하는 데 도움이 됩니다.


