Rust #11: 11장 자동화된 테스트 작성하기
개요
테스트는 작성된 코드가 의도한 대로 동작하는지 확인하는 작업입니다. Rust는 주석 및 매크로, 테스트 시행을 위해 제공되는 기본 동작 및 옵션, 단위 테스트 및 통합 테스트로 구성하는 방법을 제공합니다.
테스트 작성 방법
테스트는 코드가 예상대로 동작 하는지 확인하는 Rust 함수라고 할 수 있습니다. 테스트 함수 내용은 일반적으로 다음 세 가지 작업을 수행합니다.
- 필요한 데이터 또는 상태를 설정
- 테스트할 코드를 실행
- 결과가 기대한 것인지 주장(ASSERT)
테스트 함수의 해부
Rust에서 테스트 단위는 test 속성으로 주석이 달린 함수라고 할 수 있습니다. cargo test
를 통해 테스트 되는 단위이며, 테스트 용 실행 바이너리를 빌드하고 각 테스트 함수의 통과 또는 실패 여부를 보고합니다.
Cargo
를 이용해 새 라이브러리 프로젝트를 생성하면 기본 테스트 기능이 포함된 테스트 모듈이 자동 생성됩니다.
$ cargo new adder --lib
Created library `adder` project
$ cd adder
파일명: src/lib.rs
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}
it_works()'함수는
#[test]에 의해 테스트 함수가 됩니다.
assert_eq!`매크로는 두 값이 같은지 주장(확인)합니다. 같으면 테스트 통과이고 같지 않으면 실패가 됩니다.
$ cargo test
Compiling adder v0.1.0 (file:///projects/adder)
Finished test [unoptimized + debuginfo] target(s) in 0.57s
Running unittests (target/debug/deps/adder-92948b65e88960b4)
running 1 test
test tests::it_works ... 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
it_works
를 exploration
으로 변경하면 출력 결과 달라집니다.
$ cargo test
...
running 1 test
test tests::exploration ... ok
...
테스트 함수를 추가해 여려 개의 테스트를 수행할 수 있습니다.
#[cfg(test)]
mod tests {
#[test]
fn exploration() {
assert_eq!(2 + 2, 4);
}
#[test]
fn another() {
panic!("Make this test fail");
}
}
$ cargo test
Compiling adder v0.1.0 (file:///projects/adder)
Finished test [unoptimized + debuginfo] target(s) in 0.72s
Running unittests (target/debug/deps/adder-92948b65e88960b4)
running 2 tests
test tests::another ... FAILED
test tests::exploration ... ok
failures:
---- tests::another stdout ----
thread 'main' panicked at 'Make this test fail', src/lib.rs:10:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
tests::another
test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass '--lib'
assert! 매크로로 결과 확인하기
assert!
매크로는 true
일 때 테스트 성공, false
일 때 테스트 실패이며 panic!
을 호출합니다.
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn larger_can_hold_smaller() {
let larger = Rectangle {
width: 8,
height: 7,
};
let smaller = Rectangle {
width: 5,
height: 1,
};
assert!(larger.can_hold(&smaller));
}
}
$ cargo test
Compiling rectangle v0.1.0 (file:///projects/rectangle)
Finished test [unoptimized + debuginfo] target(s) in 0.66s
Running unittests (target/debug/deps/rectangle-6584c4561e48942e)
running 1 test
test tests::larger_can_hold_smaller ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests rectangle
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
다음은 더 작은 사각형이 더 큰 사각형을 담을 수 없다고 단언하는 또 다른 테스트를 추가 해보겠습니다.
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn larger_can_hold_smaller() {
// --snip--
}
#[test]
fn smaller_cannot_hold_larger() {
let larger = Rectangle {
width: 8,
height: 7,
};
let smaller = Rectangle {
width: 5,
height: 1,
};
assert!(!smaller.can_hold(&larger));
}
}
$ cargo test
Compiling rectangle v0.1.0 (file:///projects/rectangle)
Finished test [unoptimized + debuginfo] target(s) in 0.66s
Running unittests (target/debug/deps/rectangle-6584c4561e48942e)
running 2 tests
test tests::larger_can_hold_smaller ... ok
test tests::smaller_cannot_hold_larger ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests rectangle
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
다음에는 일부러 코드에 버그를 넣어서 이 테스트 결과가 어떻게 달라지는지 봅시다.
// --snip--
impl Rectangle {
fn can_hold(&self, other: &Rectangle) -> bool {
self.width < other.width && self.height > other.height
}
}
$ cargo test
Compiling rectangle v0.1.0 (file:///projects/rectangle)
Finished test [unoptimized + debuginfo] target(s) in 0.66s
Running unittests (target/debug/deps/rectangle-6584c4561e48942e)
running 2 tests
test tests::larger_can_hold_smaller ... FAILED
test tests::smaller_cannot_hold_larger ... ok
failures:
---- tests::larger_can_hold_smaller stdout ----
thread 'main' panicked at 'assertion failed: larger.can_hold(&smaller)', src/lib.rs:28:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
tests::larger_can_hold_smaller
test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass '--lib'
버그가 생기면서 정상적으로 동작했던 테스트 함수들의 결과를 통해 버그를 감지할 수 있게 되었습니다!
assert_eq! 및 assert_ne! 매크로를 사용하여 같음 테스트
assert!
매크로 뿐만 아니라 assert_eq!
및 assert_ne!
매크로를 통해 테스트를 진행 할 수 있습니다.
pub fn add_two(a: i32) -> i32 {
a + 2
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_adds_two() {
assert_eq!(4, add_two(2));
}
}
4
와 add_two(2)
의 결과값(4) 가 같은지 주장합니다. 같으면 테스트 성공, 같지 않으면 테스트 실패가 됩니다.
$ cargo test
Compiling adder v0.1.0 (file:///projects/adder)
Finished test [unoptimized + debuginfo] target(s) in 0.58s
Running unittests (target/debug/deps/adder-92948b65e88960b4)
running 1 test
test tests::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
add_two()
함수를 다음과 같이 수정해 버그를 만들어 봅시다.
pub fn add_two(a: i32) -> i32 {
a + 3
}
$ cargo test
Compiling adder v0.1.0 (file:///projects/adder)
Finished test [unoptimized + debuginfo] target(s) in 0.61s
Running unittests (target/debug/deps/adder-92948b65e88960b4)
running 1 test
test tests::it_adds_two ... FAILED
failures:
---- tests::it_adds_two stdout ----
thread 'main' panicked at 'assertion failed: `(left == right)`
left: `4`,
right: `5`', src/lib.rs:11:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
tests::it_adds_two
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass '--lib'
기대했던 결과가 아니므로 테스트는 실패하며 버그를 감지할 수 있습니다!
assert_ne!
매크로는 두 값이 동일하지 않을 경우 테스트 성공, 같을 경우 테스트 실패가 됩니다.
같거나 같지않음
은 모든 기본 유형과 대부분의 표준 라이브러리 유형에서 구현되어 있지만 사용자 유형의 경우 PartialEq
를 구현해야 합니다. 또 실패할 경우 출력하는 기능인 Debug
를 구현해야 합니다. 이는 파생 가능한 특성이기 때문에 #[derive(PartialEq, Debug)]
주석을 추가하는 것으로 가능합니다.
사용자 정의 실패 메시지 추가
pub fn greeting(name: &str) -> String {
format!("Hello {}!", name)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn greeting_contains_name() {
let result = greeting("Carol");
assert!(result.contains("Carol"));
}
}
이 코드의 테스트 결과는 성공합니다. 실패하도록 다음처럼 수정해봅시다.
pub fn greeting(name: &str) -> String {
String::from("Hello!")
}
$ cargo test
Compiling greeter v0.1.0 (file:///projects/greeter)
Finished test [unoptimized + debuginfo] target(s) in 0.91s
Running unittests (target/debug/deps/greeter-170b942eb5bf5e3a)
running 1 test
test tests::greeting_contains_name ... FAILED
failures:
---- tests::greeting_contains_name stdout ----
thread 'main' panicked at 'assertion failed: result.contains(\"Carol\")', src/lib.rs:12:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
tests::greeting_contains_name
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass '--lib'
Rust는 매크로의 기능으로 똑똑하게 result.contains("Carol")
이라는 메시지를 출력합니다. 하지만 사용자 메시지로 표시하고자 할 경우, 다음의 코드처럼 할 수 있습니다.
#[test]
fn greeting_contains_name() {
let result = greeting("Carol");
assert!(
result.contains("Carol"),
"Greeting did not contain name, value was `{}`",
result
);
}
$ cargo test
Compiling greeter v0.1.0 (file:///projects/greeter)
Finished test [unoptimized + debuginfo] target(s) in 0.93s
Running unittests (target/debug/deps/greeter-170b942eb5bf5e3a)
running 1 test
test tests::greeting_contains_name ... FAILED
failures:
---- tests::greeting_contains_name stdout ----
thread 'main' panicked at 'Greeting did not contain name, value was `Hello!`', src/lib.rs:12:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
tests::greeting_contains_name
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass '--lib'
`
패닉 확인 should_panic
코드가 예상한 대로 동작 하는지 확인하는 것도 중요하지만 코드가 예상대로 오류 조건을 처리하는지 확인하는 것도 중요합니다.
pub struct Guess {
value: i32,
}
impl Guess {
pub fn new(value: i32) -> Guess {
if value < 1 || value > 100 {
panic!("Guess value must be between 1 and 100, got {}.", value);
}
Guess { value }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic]
fn greater_than_100() {
Guess::new(200);
}
}
$ cargo test
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
Finished test [unoptimized + debuginfo] target(s) in 0.58s
Running unittests (target/debug/deps/guessing_game-57d70c3acb738f4d)
running 1 test
test tests::greater_than_100 - should panic ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests guessing_game
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
should_panic
속성으로 해당 함수가 panic!
되었을 때 테스트 성공으로 처리합니다!
이제 버그를 만들어서 이를 감지 하는지 확인해봅시다.
// --snip--
impl Guess {
pub fn new(value: i32) -> Guess {
if value < 1 {
panic!("Guess value must be between 1 and 100, got {}.", value);
}
Guess { value }
}
}
$ cargo test
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
Finished test [unoptimized + debuginfo] target(s) in 0.62s
Running unittests (target/debug/deps/guessing_game-57d70c3acb738f4d)
running 1 test
test tests::greater_than_100 - should panic ... FAILED
failures:
---- tests::greater_than_100 stdout ----
note: test did not panic as expected
failures:
tests::greater_than_100
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass '--lib'
멋지네요. 그런데 panic!
이 테스트하고자 하는 원인으로 발생하지 않을 수 있습니다. 이런 경우 테스트 실패가 되어야 하는데 다음 코드처럼 expected
에 panic!
문자열을 주어 이것을 구분할 수 있습니다.
// --snip--
impl Guess {
pub fn new(value: i32) -> Guess {
if value < 1 {
panic!(
"Guess value must be greater than or equal to 1, got {}.",
value
);
} else if value > 100 {
panic!(
"Guess value must be less than or equal to 100, got {}.",
value
);
}
Guess { value }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic(expected = "Guess value must be less than or equal to 100")]
fn greater_than_100() {
Guess::new(200);
}
}
이제 버그를 만들어 봅시다.
if value < 1 {
panic!(
"Guess value must be less than or equal to 100, got {}.",
value
);
} else if value > 100 {
panic!(
"Guess value must be greater than or equal to 1, got {}.",
value
);
}
$ cargo test
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
Finished test [unoptimized + debuginfo] target(s) in 0.66s
Running unittests (target/debug/deps/guessing_game-57d70c3acb738f4d)
running 1 test
test tests::greater_than_100 - should panic ... FAILED
failures:
---- tests::greater_than_100 stdout ----
thread 'main' panicked at 'Guess value must be greater than or equal to 1, got 200.', src/lib.rs:13:13
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
note: panic did not contain expected string
panic message: `"Guess value must be greater than or equal to 1, got 200."`,
expected substring: `"Guess value must be less than or equal to 100"`
failures:
tests::greater_than_100
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass '--lib'
테스트 실패로 버그를 감지할 수 있습니다.
Result 테스트에서 사용
다음의 코드를 봅시다.
#[cfg(test)]
mod tests {
#[test]
fn it_works() -> Result<(), String> {
if 2 + 2 == 4 {
Ok(())
} else {
Err(String::from("two plus two does not equal four"))
}
}
}
결과가 Ok
이면 테스트 성공, Err
이면 테스트 실패이며, 메시지가 출력 됩니다.
또한 panic!
이 호출되었을 때 테스트가 실패하므로 물음표 연산자를 통해 위의 코드를 좀 더 단순하게 만들 수도 있습니다.
Result<T, E>
를 반환하는 테스트 함수에서는 #[should_panic]
을 사용할 수 없습니다.
테스트 실행 방법 제어
cargo run
이 코드를 컴파일 한 후 바이너리를 실행하는 것 처럼 cargo test
는 테스트 모드에서 코드를 컴파일하고 테스트 바이너리를 실행합니다. 명령 옵션을 통해 기본 동작을 바꿀 수 있습니다.
병렬 또는 연속으로 테스트 실행
테스트를 더 빨리 끝내기 위해 테스트 스레드를 지정해서 병렬로 테스트를 진행할 수 있습니다.
$ cargo test -- --test-threads=1
테스트 환경이 병렬로 진행할 수 있는지 확인한 후 테스트 스레드 개수를 늘려 테스트 시간을 단축할 수 있습니다.
함수 출력 표시
특별한 인자를 주지 않을 경우 테스트 중에는 테스트가 성공했을 때는 표준 출력을 캡쳐 하지 않습니다.
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
Compiling silly-function v0.1.0 (file:///projects/silly-function)
Finished test [unoptimized + debuginfo] target(s) in 0.58s
Running unittests (target/debug/deps/silly_function-160869f38cff9166)
running 2 tests
test tests::this_test_will_fail ... FAILED
test tests::this_test_will_pass ... ok
failures:
---- tests::this_test_will_fail stdout ----
I got the value 8
thread 'main' panicked at 'assertion failed: `(left == right)`
left: `5`,
right: `10`', src/lib.rs:19:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
tests::this_test_will_fail
test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass '--lib'
다음의 인자를 주어 테스트가 성공했을 때도 표준 출력으로 캡쳐할 수 있습니다.
$ cargo test -- --show-output
$ cargo test -- --show-output
Compiling silly-function v0.1.0 (file:///projects/silly-function)
Finished test [unoptimized + debuginfo] target(s) in 0.60s
Running unittests (target/debug/deps/silly_function-160869f38cff9166)
running 2 tests
test tests::this_test_will_fail ... FAILED
test tests::this_test_will_pass ... ok
successes:
---- tests::this_test_will_pass stdout ----
I got the value 4
successes:
tests::this_test_will_pass
failures:
---- tests::this_test_will_fail stdout ----
I got the value 8
thread 'main' panicked at 'assertion failed: `(left == right)`
left: `5`,
right: `10`', src/lib.rs:19:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
tests::this_test_will_fail
test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass '--lib'
이름으로 테스트 하위 집합 실행
다음의 테스트 코드가 있을 때,
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 (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 test one_hundred
Compiling adder v0.1.0 (file:///projects/adder)
Finished test [unoptimized + debuginfo] target(s) in 0.69s
Running unittests (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
여러 테스트를 실행하기 위한 필터링
특정 이름으로 두 개 이상의 테스트 함수를 실행할 수 있습니다.
$ cargo test add
Compiling adder v0.1.0 (file:///projects/adder)
Finished test [unoptimized + debuginfo] target(s) in 0.61s
Running unittests (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
특별히 요청하지 않는 한 일부 테스트 무시
ignore
속성이 부여되었을 경우 해당 테스트 함수는 전체 테스트에서 제외됩니다.
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
#[test]
#[ignore]
fn expensive_test() {
// code that takes an hour to run
}
$ cargo test
Compiling adder v0.1.0 (file:///projects/adder)
Finished test [unoptimized + debuginfo] target(s) in 0.60s
Running unittests (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.02s
Doc-tests adder
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
하지만 ignore
옵션을 주었을 경우 해당 테스트 함수가 실행되게 됩니다.
$ cargo test -- --ignored
Compiling adder v0.1.0 (file:///projects/adder)
Finished test [unoptimized + debuginfo] target(s) in 0.61s
Running unittests (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
테스트 구조
테스트는 복잡한 분야이며 사람들마다 다른 용어와 구조를 사용합니다. Rust에서는 단위 테스트와 통합 테스트라는 두 가지 주요 범주에서 테스트를 바라봅니다.
두 종류의 테스트를 작성하는 것은 라이브러리가 자체 또는 이용하는 측면에서 기대하는 대로 동작 하는지 확인하는데 중요합니다.
단위 테스트
단위 테스트의 목적은 자체적으로 각 기능이 정상적으로 동작하는지 확인하는데 목적이 있습니다. 라이브러리 내부의 기능이 잘 동작 하는 것을 확인합니다.
테스트 모듈 및 #[cfg(test)]
#[cfg(test)]
는 cargo build
에는 포함되지 않고 cargo test
로 테스트 중에만 해당 모듈이 포함되도록 Rust에 지시 합니다.
그러나 통합 테스트는 다른 디렉토리로 이동하기 때문에 #[cfg(test)]
주석이 필요하지 않습니다. 그러나 단위 테스트는 일반 코드와 동일한 코드에 있기 때문에 #[cfg(test)]
주석을 주어 빌드 시 바이너리에 포함되지 않도록 합니다.
앞 전에 addr
라이브러리 프로젝트를 생성했을 떄 다음과 같이 단위 테스트 코드가 생성된 것을 확인할 수 있었습니다.
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}
비공개 기능 테스트
비공개 기능을 직접 테스트해야 하는지에 대한 논쟁이 있지만 Rust는 이를 허용합니다.
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));
}
}
통합 테스트
통합 테스트는 라이브러리가 정상적으로 동작하는지 대상인 라이브러리의 외부에서 테스트를 진행합니다. 마치 라이브러리를 이용하는 것처럼 동일한 방식으로 공개된 API를 호출합니다. 그러므로 단위 테스트와는 다르게 비공개 기능에는 접근할 수 없습니다. 실제로 사용하는 관점에서 테스트를 수행하므로 통합 테스트 역시 단위 테스트 만큼 중요합니다.
tests
디렉토리
Rust는 tests
디렉토리가 통합 테스트를 위한 디렉토리라는 것을 알고 있습니다.
파일 이름: tests/integration_test.rs
use adder;
#[test]
fn it_adds_two() {
assert_eq!(4, adder::add_two(2));
}
라이브러리와는 별도의 테스트이므로 #[cfg(test)]
주석이 빠진다는 것을 알 수 있습니다.
$ cargo test
Compiling adder v0.1.0 (file:///projects/adder)
Finished test [unoptimized + debuginfo] target(s) in 1.31s
Running unittests (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 --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
통합 테스트의 하위 모듈
통합 테스트에도 일반 코드 모듈처럼 하위 모듈로 구성해서 테스트를 진행할 수 있습니다. 다음은 테스트 함수에서 초기화 등을 이유로 공통으로 호출해야 하는 함수가 있을 경우 common.rs
등으로 만들고 호출해서 사용할 수 있습니다.
파일 이름: tests/common.rs
pub fn setup() {
// setup code specific to your library's tests would go here
}
$ cargo test
Compiling adder v0.1.0 (file:///projects/adder)
Finished test [unoptimized + debuginfo] target(s) in 0.89s
Running unittests (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
그런데 테스트 대상에 해당 함수가 포함되었습니다. 우리가 원하는 결과는 아닙니다. 이것을 해결하려면 tests/common.rs
대신 tests/common/mod.rs
의 위치에 만듭니다. 다음은 테스트 함수에서 common모듈을 호출하는 예입니다.
use adder;
mod common;
#[test]
fn it_adds_two() {
common::setup();
assert_eq!(4, adder::add_two(2));
}
바이너리 크레이트 통합 테스트
Rust는 라이브러리 크레이트만 통합 테스트를 지원합니다.
정리
Rust의 테스트 기능 코드가 변경 되었을 때 코드가 정상 동작함을 확인하는 방법을 제공합니다. 단위 테스트는 라이브러리의 여러 부분을 개별적으로 실행하고 비공개 구현 세부 정보를 테스트할 수 있습니다. 통합 테스트는 종합적으로 라이브러리와 함께 외부 코드에서 사용하는 것과 동일한 방식으로 코드를 테스트 합니다.