반응형
Rust에서 테스트코드 작성하는 법
- lib 프로젝트 생성한다.
cargo new adder --lib
- lib 프로젝트를 만들면 아래와 같이 src/lib.rs에 아래와 같은 테스트 코드가 작성되어 있다.
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}
- test 돌리기
cargo test
- 테스트 하고 싶은 함수는 함수 위에
#[test]
라고 붙여주면 된다. 일단 mod포함 상단 부분은 신경쓰지 말자.
#[cfg(test)]
mod tests {
#[test]
fn exploration() {
assert_eq!(2 + 2, 4);
}
}
assert! Macro로 결과 확인하기
assert!
매크로는 값이true
인지 확인한다.false
라면panic!
호출한다.
// src/lib.rs
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
#[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));
}
}
assert_eq! 와 assert_ne! Macros로 결과 비교하기
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));
}
}
커스터마이즈된 실패 메시지 출력하기
assert!
,assert_eq!
,assert_ne!
의 필수 인자 뒤에 커스텀 메시지를 추가 가능하다. 내부적으로format!
매크로를 호출하기 때문에{}
placeholdrs와 placeholders에 들어갈 값들을 써줄 수 있다.
pub fn greeting(name: &str) -> String {
String::from("Hello!")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn greeting_contains_name() {
let result = greeting("Carol");
assert!(
result.contains("Carol"),
"Greeting did not contain name, value was `{}`",
result
);
}
}
should_panic으로 패닉 테스트하기
#[test]
의 아래에#[should_panic]
을 써주면 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);
}
}
- panic 메시지 역시 테스트 가능하다.
pub struct Guess {
value: i32,
}
// --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);
}
}
테스트에서 Result<T, E> 사용하기
- 실패했을 때 panic하는 대신에
Result<T, E>
를 사용할 수 있다. - 리턴 타입을
Result<T, E>
로 사용한다. - 이렇게 하면 테스트 본문에서
?
를 사용할 수 있게 해준다. - 단,
#[should_panic]
을 사용할 수 없다.
#[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"))
}
}
}
테스트 플로우 컨트롤하기
테스트를 병렬적/순차적으로 실행하기
- 기본적으로 Rust의 테스트는 쓰레드를 활용하여 병렬적으로 돌아간다.
- 만약 같은 파일에 무언가를 쓰는 테스트가 있다면 정상적인 코드라도 날 확률이 있다.
- 이 때는 다른 파일을 쓰거나, 테스트를 한 번에 하나씩만 돌아가게 한다.
- 아래의 옵션을 통해 하나의 쓰레드에서 돌아가도록 할 수 있다.
cargo test -- --test-threads=1
함수의 결과 출력하기
- 기본적으로 Rust에서는 함수에서 사용한
println!
을 보여주지 않는다. - 아웃풋을 보고 싶다면 다음의 커맨드 사용.한다
cargo test -- --show-output
이름으로 일부 테스트만 실행하기
// src/lib.rs
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));
}
}
one_hundred
하나의 함수를 테스트하고 싶다면
cargo test one_hundred
- add라는 이름을 포함한 여러개의 테스트를 돌리고 싶다면
cargo test add
- 위의 커맨드를 실행하면
add_two_and_two
,add_three_and_two
함수가 테스트 된다.
무거운 테스트 무시하기
- 시간이 오래걸리는 테스트는
ignore
속성으로 테스트에서 제외할 수 있다.
// src/lib.rs
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
#[test]
#[ignore]
fn expensive_test() {
// code that takes an hour to run
}
- 이렇게 하면,
expensive_test
는 테스트시에 테스트 되지 않는다. ignore
된 테스트는 아래의 커맨드로 따로 돌릴 수 있다.
cargo test -- --ignored
테스트 조직화
- 유닛테스트
- 작고, 더 포커스되어 있음
- 한 번에 하나의 모듈을 독립적으로 테스트함
- private 인터페이스도 테스트 가능
- 통합테스트
- 라이브러리와 분리
- 외부 코드가 사용하는 방식과 똑같이 코드를 사용
- 오로지 public 인터페이스만 테스트
- 테스트당 여러개의 모듈을 활용할 가능
유닛 테스트 (Unit Tests)
- 컨벤션: 각 파일 안에
tests
라는 이름을 가진 모듈을 만든다. 해당 모듈은 테스트 함수를 포함하고 그 모듈을cfg(test)
로 어노테이트 한다.
tests 모듈과 #[cfg(tests)]
- 유닛테스트에서
#[cfg(tests)]
가 들어가면, 해당 모듈은 테스트 시에만 빌드
// src/lib.rs
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}
Private 함수 테스트하기
- 러스트에서는 private함수도 테스트 가능하다.
// 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));
}
}
통합테스트 (Integration Tests)
- 통합테스트는 완전 라이브러리와 분리되어 있다.
- 다른 코드가 라이브러리를 사용하는 방식과 동리하게 사용한다. 즉, 라이브러리의 public API만을 호출할 수 있다.
- 통합테스트의 목적은 라이브러리의 많은 부분이 함께 올바르게 동작할 수 있는지를 테스트하는 것이다.
tests 디렉토리
- 프로젝트 최상단 디렉토리에 tests디렉토리를 만든다. (src와 동등한 위치)
- tests디렉토리에 파일을 만든다.
- tests내의 파일은 각각 별개의 crate이므로 라이브러리를 테스트 crate의 scope로 가져와야함. (
use adder
)
// tests/integration_test.rs
use adder;
#[test]
fn it_adds_two() {
assert_eq!(4, adder::add_two(2));
}
- 특정 테스트만 실행
cargo test --test integration_test
통합테스트의 서브모듈
- 많은 테스트에서 공통으로 사용되는 부분을 모아서 helper 함수들을 만들면 유용할 수 있다. 그냥 tests디렉토리 내에 파일을 만들면 해당 파일역시 테스트로 인식되므로 다음과 같은 tests디렉토리내에 common이라는 폴더를 만들고 그 안에 mod.rs를 만든다.
// tests/common/mod.rs
pub fn setup() {
// setup code specific to your library's tests would go here
}
- 이는 통합테스트 어느곳에서나 사용가능하다.
// tests/integration_test.rs
use adder;
mod common;
#[test]
fn it_adds_two() {
common::setup();
assert_eq!(4, adder::add_two(2));
}
Binary Crates를 위한 통합 테스트
만약 프로젝트가 src/main.rs만을 포함하고 있는 binary crate라면 통합테스트를 만들 수 없다. 오로지 library crates만이 다른 crates가 사용할 수 있는 함수를 노출 시 킬 수 있다.
반응형
'개발 > Rust' 카테고리의 다른 글
Rust 버전의 노마드 코인 nomadcoin-rs (19) | 2024.02.17 |
---|---|
[TIL] Rust 공부 : Attribute, Modules, Null in Rust 등 (0) | 2020.10.18 |