유닛 테스트
테스트는 테스트 대상 코드가 예상대로 작동하는지 확인하는 Rust 함수입니다. 테스트 함수의 본문은 일반적으로 설정을 수행하고, 테스트하려는 코드를 실행한 다음, 결과가 예상과 일치하는지 단언(assert)합니다.
대부분의 유닛 테스트는 #[cfg(test)] 속성이 있는 tests 모듈에 들어갑니다. 테스트 함수는 #[test] 속성으로 표시됩니다.
테스트 함수 내에서 패닉이 발생하면 테스트가 실패합니다. 다음과 같은 몇 가지 도우미 매크로가 있습니다:
assert!(expression)- 표현식의 평가 결과가false이면 패닉을 발생시킵니다.assert_eq!(left, right)및assert_ne!(left, right)- 왼쪽과 오른쪽 표현식이 각각 같은지 또는 다른지를 테스트합니다.
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
// 이것은 정말 형편없는 더하기 함수입니다. 이 예제에서 실패하도록 만드는 것이 목적입니다.
#[allow(dead_code)]
fn bad_add(a: i32, b: i32) -> i32 {
a - b
}
#[cfg(test)]
mod tests {
// 이 유용한 관용구를 기억하세요: 외부 스코프(mod tests의 경우)에서 이름들을 가져옵니다.
use super::*;
#[test]
fn test_add() {
assert_eq!(add(1, 2), 3);
}
#[test]
fn test_bad_add() {
// 이 단언(assert)은 실행될 것이고 테스트는 실패할 것입니다.
// 참고로, 프라이빗 함수도 테스트할 수 있습니다!
assert_eq!(bad_add(1, 2), 3);
}
}
테스트는 cargo test로 실행할 수 있습니다.
$ cargo test
running 2 tests
test tests::test_bad_add ... FAILED
test tests::test_add ... ok
failures:
---- tests::test_bad_add stdout ----
thread 'tests::test_bad_add' panicked at 'assertion failed: `(left == right)`
left: `-1`,
right: `3`', src/lib.rs:21:8
note: Run with `RUST_BACKTRACE=1` for a backtrace.
failures:
tests::test_bad_add
test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out
테스트와 ?
이전의 유닛 테스트 예제들에는 반환 타입이 없었습니다. 하지만 Rust 2018에서는 유닛 테스트가 Result<()>를 반환할 수 있어, 테스트 내에서 ?를 사용할 수 있습니다! 이를 통해 테스트를 훨씬 더 간결하게 만들 수 있습니다.
fn sqrt(number: f64) -> Result<f64, String> {
if number >= 0.0 {
Ok(number.powf(0.5))
} else {
Err("음수 부동 소수점은 제곱근을 가질 수 없습니다".to_owned())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sqrt() -> Result<(), String> {
let x = 4.0;
assert_eq!(sqrt(x)?.powf(2.0), x);
Ok(())
}
}
더 자세한 내용은 “The Edition Guide”를 참조하세요.
패닉 테스트
특정 상황에서 패닉이 발생해야 하는 함수를 확인하려면 #[should_panic] 속성을 사용합니다. 이 속성은 선택적 파라미터 expected = 를 받으며, 패닉 메시지의 텍스트를 확인합니다. 만약 여러분의 함수가 여러 방식으로 패닉을 일으킬 수 있다면, 이 파라미터는 예상한 패닉이 발생했는지 확인하는 데 도움이 됩니다.
참고: Rust는 #[should_panic = "message"]와 같은 단축 형태도 허용하며, 이는 #[should_panic(expected = "message")]와 정확히 똑같이 작동합니다. 둘 다 유효하지만, 후자가 더 일반적으로 사용되며 더 명시적인 것으로 간주됩니다.
pub fn divide_non_zero_result(a: u32, b: u32) -> u32 {
if b == 0 {
panic!("0으로 나누기 에러");
} else if a < b {
panic!("나눗셈 결과가 0입니다");
}
a / b
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_divide() {
assert_eq!(divide_non_zero_result(10, 2), 5);
}
#[test]
#[should_panic]
fn test_any_panic() {
divide_non_zero_result(1, 0);
}
#[test]
#[should_panic(expected = "나눗셈 결과가 0입니다")]
fn test_specific_panic() {
divide_non_zero_result(1, 10);
}
#[test]
#[should_panic = "나눗셈 결과가 0입니다"] // 이 방식도 작동합니다
fn test_specific_panic_shorthand() {
divide_non_zero_result(1, 10);
}
}
이 테스트들을 실행하면 다음과 같은 결과를 얻습니다:
$ cargo test
running 4 tests
test tests::test_any_panic ... ok
test tests::test_divide ... ok
test tests::test_specific_panic ... ok
test tests::test_specific_panic_shorthand ... ok
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Doc-tests tmp-test-should-panic
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
특정 테스트 실행하기
특정 테스트를 실행하려면 cargo test 명령에 테스트 이름을 지정하면 됩니다.
$ cargo test test_any_panic
running 1 test
test tests::test_any_panic ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 3 filtered out
Doc-tests tmp-test-should-panic
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
여러 테스트를 실행하려면 실행하려는 모든 테스트와 매칭되는 테스트 이름의 일부분을 지정하면 됩니다.
$ cargo test panic
running 3 tests
test tests::test_any_panic ... ok
test tests::test_specific_panic ... ok
test tests::test_specific_panic_shorthand ... ok
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out
Doc-tests tmp-test-should-panic
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
테스트 무시하기
일부 테스트를 제외하기 위해 #[ignore] 속성을 표시할 수 있습니다. 또는 cargo test -- --ignored 명령으로 무시된 테스트들만 실행할 수도 있습니다.
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() {
assert_eq!(add(2, 2), 4);
}
#[test]
fn test_add_hundred() {
assert_eq!(add(100, 2), 102);
assert_eq!(add(2, 100), 102);
}
#[test]
#[ignore]
fn ignored_test() {
assert_eq!(add(0, 0), 0);
}
}
$ cargo test
running 3 tests
test tests::ignored_test ... ignored
test tests::test_add ... ok
test tests::test_add_hundred ... ok
test result: ok. 2 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out
Doc-tests tmp-ignore
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
$ cargo test -- --ignored
running 1 test
test tests::ignored_test ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Doc-tests tmp-ignore
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out