홈강좌Rust 입문·중급 47편 중 42편째
Rust 테스트 — #[test] 를 붙이고 cargo test 로 돌린다
Rust 테스트는 #[test] 를 붙인 함수이고, cargo test 가 모두 찾아 돌린다. assert_eq! 로 기대값과 실제 값을 비교하고, 틀리면 left·right 두 값을 찍으며 실패한다. 패닉해야 맞는 동작은 #[should_panic] 으로 시험한다. 테스트는 #[cfg(test)] 모듈에 두어 실제 빌드에는 들어가지 않는다.
01 테스트를 쓰고 돌린다
40편의 세금 계산 함수에 테스트를 붙여 보자.
fn with_tax(price: u32) -> u32 {
price + price / 10
}
fn main() {
println!("{}", with_tax(12000));
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn adds_ten_percent() {
assert_eq!(with_tax(12000), 13200);
}
#[test]
fn zero_stays_zero() {
assert_eq!(with_tax(0), 0);
}
}cargo test Compiling price v0.1.0 (~/projects/price)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.17s
Running unittests src/main.rs (target/debug/deps/price-b3d7118fe58af1a1)
running 2 tests
test tests::adds_ten_percent ... ok
test tests::zero_stays_zero ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s#[cfg(test)]는 "테스트할 때만 컴파일하라" 는 표시다.cargo run·cargo build에는 이 모듈이 들어가지 않는다.mod tests는 40편의 모듈이다.use super::*;는 "바깥(부모) 모듈의 것을 모두 불러온다" 는 뜻이라,with_tax를 그대로 부른다.#[test]를 붙인 함수 하나가 테스트 하나다. 이름은 무엇을 확인하는지 문장처럼 짓는다.assert_eq!(실제, 기대)는 두 값이 같은지 본다. 같으면 지나가고, 다르면 테스트가 실패한다.
출력은 테스트마다 ok 를 찍고, 마지막 줄에 2 passed; 0 failed 처럼 모은다. Running unittests src/main.rs (…) 의 괄호 안은 테스트용으로 따로 만든 실행 파일이다.
02 실패하면 두 값을 보여 준다
작은 가격도 시험해 보자. 15원의 10% 는 1.5원이니 올려서 17원이 되리라 기대했다.
fn with_tax(price: u32) -> u32 {
price + price / 10
}
fn main() {
println!("{}", with_tax(12000));
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn adds_ten_percent() {
assert_eq!(with_tax(12000), 13200);
}
#[test]
fn rounds_small_prices() {
assert_eq!(with_tax(15), 17);
}
}running 2 tests
test tests::adds_ten_percent ... ok
test tests::rounds_small_prices ... FAILED
failures:
---- tests::rounds_small_prices stdout ----
thread 'tests::rounds_small_prices' (213159569) panicked at src/main.rs:20:9:
assertion `left == right` failed
left: 16
right: 17
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
tests::rounds_small_prices
test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00srounds_small_prices 가 실패했다(FAILED). 실패한 테스트마다 패닉 메시지를 찍는다.
assertion left == right failed—assert_eq!의 두 값이 달랐다.left: 16이 실제 값(with_tax(15)),right: 17이 기대값이다.src/main.rs:20:9가 실패한assert_eq!의 자리다.
5편에서 본 대로 정수 나눗셈 15 / 10 은 1 이라서 16 이 나왔다. 테스트가 틀린 것인지 코드가 틀린 것인지는 사람이 정한다. 반올림이 맞는 규칙이라면 코드를 고치고, 버림이 맞다면 테스트의 기대값을 16 으로 고친다. 마지막 줄의 종료 코드가 0 이 아니어서, 다른 도구가 "테스트 실패" 를 알아챌 수 있다.
03 실습 — 패닉해야 맞는 동작 시험하기
30편의 재고 함수는 재고보다 많이 꺼내면 패닉해야 맞다. 이런 동작도 시험한다.
fn take(stock: u32, n: u32) -> u32 {
if n > stock {
panic!("not enough stock");
}
stock - n
}
fn main() {
println!("{}", take(5, 3));
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn takes_some() {
assert_eq!(take(5, 3), 2);
}
#[test]
#[should_panic(expected = "not enough stock")]
fn refuses_too_many() {
take(5, 9);
}
#[test]
fn result_is_positive() {
assert!(take(5, 5) == 0, "taking everything should leave zero");
}
} Compiling stock v0.1.0 (~/projects/stock)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.19s
Running unittests src/main.rs (target/debug/deps/stock-61a6a537e2c6b74d)
running 3 tests
test tests::result_is_positive ... ok
test tests::takes_some ... ok
test tests::refuses_too_many - should panic ... ok
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s#[should_panic(expected = "not enough stock")]는 "이 테스트는 패닉해야 통과, 메시지에 이 글자가 들어 있어야 한다" 다. 출력에should panic ... ok로 찍혔다.assert!(조건, "메시지")는 조건이true인지 본다. 실패하면 뒤의 메시지를 찍는다. 같음 비교가 아닌 조건을 볼 때 쓴다.- 테스트 순서는 이름 순서가 아니다. 테스트는 기본으로 여러 스레드에서 동시에 돌기 때문이다(다음 편).
with_tax 의 반올림을 고쳐서 test-fail 의 두 테스트를 모두 통과시켜 보자. 47편의 wordfreq 2판은 세는 부분에 테스트 넷을 붙인다. 다음 편부터는 동시성이다. 첫 주제는 스레드다.