테스팅 완료로 변경

sarojaba authored
revision dd7f15abab4b86619b35672c6962b1e8a0dbf41f
testing
# 4.2. 테스팅 (Testing) - 5100%

> 프로그램 테스팅은 버그의 존재를 보이는 매우 효과적인 방법일 수 있지만,
> 버그가 없다는 걸 보이는 데는 절망적으로 부적합하다.
>
> 에츠허르 W. 데이크스트라, "The Humble Programmer" (1972)

Rust 코드를 테스트하는 방법에 대해 이야기해봅시다. 여기에서는 Rust 코드를 테스트하는
올바른 방법에 대해서 얘기하지는 않을 것입니다. 어떻게 테스트를 작성하는 것이 옳고 그른가는
많은 의견들이 있지만, 이들 모두 같은 기본 도구를 쓰기 때문에, 여기에서는 이 도구를 쓰는
문법을 보여 주려고 합니다.

# `test` 속성

간단히 말해서, Rust에서 테스트는 `test` 속성이 표기된 함수입니다. Cargo를 써서 `adder`라는 이름의 새 프로젝트를 만들어 볼까요?

```bash
$ cargo new adder
$ cd adder
```

새 프로젝트를 만들면 Cargo는 자동으로 간단한 테스트를 생성해 줍니다.
다음은 `src/lib.rs`의 내용입니다.

```rust
#[test]
fn it_works() {
}
```

`#[test]`를 눈여겨 보세요. 이 속성은 이 함수가 테스트 함수임을 나타냅니다. 지금은
안에 아무 것도 없는데, 이러면 물론 테스트는 성공하겠죠! `cargo test`로 테스트를 돌릴 수
있습니다.

```bash
$ cargo test
Compiling adder v0.0.1 (file:///home/you/projects/adder)
Running target/adder-91b3e234d4ed382a

running 1 test
test it_works ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured

Doc-tests adder

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured
```

Cargo가 테스트를 컴파일하고 실행했습니다. 여기에는 두 종류의 출력이 있는데, 하나는
우리가 작성한 테스트에 해당하고, 다른 하나는 문서 테스트에 해당합니다. 문서 테스트에
대해서는 나중에 이야기할 것입니다. 우선은 이 줄을 보지요.

```text
test it_works ... ok
```

`it_works`는 우리가 짠 테스트 함수의 이름입니다.

```rust
fn it_works() {
# }
```

테스트 결과에 대한 요약도 볼 수 있습니다.

```text
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
```

그럼 왜 아무 것도 하지 않는 테스트가 성공하는 걸까요? `panic!`을 실행하지 않는 모든 테스트는
성공한 걸로 치고, `panic!`을 실행하는 테스트는 실패한 걸로 칩니다. 위 테스트가 실패하도록
만들어 봅시다.

```rust
#[test]
fn it_works() {
assert!(false);
}
```

`assert!`는 Rust가 제공하는 매크로로, 인자 하나를 받아서 그게 `true`면 아무 일도 하지 않고,
`false`면 `panic!`을 호출합니다. 한 번 테스트를 다시 돌려 보지요.

```bash
$ cargo test
Compiling adder v0.0.1 (file:///home/you/projects/adder)
Running target/adder-91b3e234d4ed382a

running 1 test
test it_works ... FAILED

failures:

---- it_works stdout ----
thread 'it_works' panicked at 'assertion failed: false', /home/steve/tmp/adder/src/lib.rs:3



failures:
it_works

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured

thread '
' panicked at 'Some tests failed', /home/steve/src/rust/src/libtest/lib.rs:247
```

Rust가 테스트가 실패했음을 알려 주었고...

```text
test it_works ... FAILED
```

결과 요약에도 반영되어 있네요.

```text
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured
```

또한 이는 0이 아닌 상태 코드를 반환하는데...

```bash
$ echo $?
101
```

이 상태 코드는 `cargo test`를 다른 도구와 연동할 때 유용합니다.

테스트가 어떨 때 성공하고 어떨 때 실패하는지를 또 다른 속성으로 뒤집을 수 있습니다.
바로 `should_panic`입니다.

```rust
#[test]
#[should_panic]
fn it_works() {
assert!(false);
}
```

이제 이 테스트는 `panic!`을 호출하면 성공하고 그렇지 않고 끝까지 실행되면 실패합니다.
다시 실행해 보죠.

```bash
$ cargo test
Compiling adder v0.0.1 (file:///home/you/projects/adder)
Running target/adder-91b3e234d4ed382a

running 1 test
test it_works ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured

Doc-tests adder

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured
```

Rust는 `assert_eq!`라는 또 다른 매크로를 제공하는데, 이 매크로는 두 인자가 같은지 검사합니다.

```rust
#[test]
#[should_panic]
fn it_works() {
assert_eq!("Hello", "world");
}
```

이 테스트가 성공할까요 실패할까요? `should_panic` 속성이 있으므로, 이 테스트는 성공해야 합니다.

```bash
$ cargo test
Compiling adder v0.0.1 (file:///home/you/projects/adder)
Running target/adder-91b3e234d4ed382a

running 1 test
test it_works ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured

Doc-tests adder

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured
```

`should_panic` 테스트는 잘못 동작하기 쉬운데, 테스트가 미처 예상하지 못한 이유로
실패하지 않는다는 걸 보장하기 어렵기 때문입니다. 이를 피하기 위해 `should_panic` 속성에
`expected` 인자를 더할 수 있습니다. 이러면 실패 메시지에 지정한 텍스트가 들어 있을 때만
테스트가 성공할 것입니다. 위 예제를 다음과 같이 좀 더 안전하게 쓸 수 있습니다.

```rust
#[test]
#[should_panic(expected = "assertion failed")]
fn it_works() {
assert_eq!("Hello", "world");
}
```

기본적인 건 이걸로 되었으니, '진짜' 테스트를 작성해 보지요.

```rust,ignore
pub fn add_two(a: i32) -> i32 {
a + 2
}

#[test]
fn it_works() {
assert_eq!(4, add_two(2));
}
```

이와 같이, 알려진 인자로 함수를 호출한 뒤 예상되는 결과와 비교하는 건
`assert_eq!`를 쓰는 흔한 방법입니다.

# `tests` 모듈

우리가 위에서 작성한 예제 중에 자연스럽지 않은 부분이 하나 있는데, 그것은 `tests` 모듈을 빼먹은 것입니다. 우리가 작성한 예제를 자연스럽게 고치면 다음과 같습니다.

```rust,ignore
pub fn add_two(a: i32) -> i32 {
a + 2
}

#[cfg(test)]
mod tests {
use super::add_two;

#[test]
fn it_works() {
assert_eq!(4, add_two(2));
}
}
```

몇가지가 바뀌었는데, 첫번째는 `cfg` 속성과 함께 `mod tests`를 도입한 것입니다. 이 모듈은 모든 우리의 테스트들을 함께 묶는 것을 허용하며, 필요하다면 helper function들을 정의할 수 있도록 해주고, 이것들이 우리 크레이트의 일부분이 되지 않도록 해 줍니다. `cfg` 속성은 우리가 테스트를 진행할 때만 테스트 코드를 컴파일하도록 합니다. 이것은 컴파일하는 시간을 절약해주며, 우리의 테스트가 완전히 일반 빌드(normal build)에서 제외되는 것을 보장해 줍니다.

두번째 차이점은 `use` 선언입니다. 우리가 안쪽 모듈의 안에 있기 때문에, 우리는 우리가 테스트할 함수를 스코프(scope) 안으로 가져올 필요가 있습니다. 만약 큰 모듈에 대해서라면 모든 함수를 일일이 추가하는 것은 무척 짜증나는 작업이 될 것이고, 따라서 `glob` 기능을 사용할 필요가 있습니다. 이 기능을 사용하도록 `src/lib.rs`를 고쳐봅시다.

```rust,ignore

pub fn add_two(a: i32) -> i32 {
a + 2
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn it_works() {
assert_eq!(4, add_two(2));
}
}
```

`use`가 사용된 행을 확인해봅시다. '\*'가 보입니까? 이제 테스트를 해 봅시다.

```bash
$ cargo test
Updating registry `https://github.com/rust-lang/crates.io-index`
Compiling adder v0.0.1 (file:///home/you/projects/adder)
Running target/adder-91b3e234d4ed382a

running 1 test
test tests::it_works ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured

Doc-tests adder

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured
```

잘 돌아갑니다!

현재의 관례상, `tests` 모듈은 자그마한 개별 기능을 시험하는 테스트들을 넣는데 주로
쓰입니다. 이른바 "유닛" 테스트인데요, 하지만 "통합" 테스트라면 어떻게 할까요?
`tests` 디렉토리는 바로 이 용도로 쓰입니다.

# `tests` 디렉토리

통합 테스트를 작성해 봅시다. 먼저 `tests` 디렉토리를 만들고, `tests/lib.rs` 파일을
안에 만들어서 내용을 이렇게 채웁니다.

```rust,ignore
extern crate adder;

#[test]
fn it_works() {
assert_eq!(4, adder::add_two(2));
}
```

지금껏 짰던 테스트와 비슷해 보이지만 조금 다른 게 있습니다. 맨 위에 `extern crate adder`가
들어간 게 보일 것입니다. `tests` 디렉토리 안에 있는 테스트들은 완전히 독립된 crate이므로,
우리가 짠 라이브러리를 테스트하려면 먼저 불러 들여야 합니다. 라이브러리를 쓰는 방법을 그대로
테스트하기 때문에 `tests`는 통합 테스트를 넣기에 좋은 장소입니다.

실행해 보지요.

```bash
$ cargo test
Compiling adder v0.0.1 (file:///home/you/projects/adder)
Running target/adder-91b3e234d4ed382a

running 1 test
test tests::it_works ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured

Running target/lib-c18e7d3494509e74

running 1 test
test it_works ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured

Doc-tests adder

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured
```

이제 출력이 세 개로 늘어났습니다. 원래 짰던 테스트는 그대로, 새 테스트가 추가되었습니다.

`tests` 디렉토리만 있으면 됩니다. 모든 것들이 tests에 집중되어 있기 때문에, `tests`는 여기에 필요치 않습니다.
That's all there is to the `tests` directory. The `tests` module isn't needed
here, since the whole thing is focused on tests.

이제 마지막으로 세번째 부분인 문서 테스트를 확인해 봅시다.
Let's finally check out that third section: documentation tests.

# 문서 테스트

문서와 예제보다 중요한 것은 없습니다. 문서가 쓰여진 이후에 코드가 변경되었기 때문에 작동하지 않는 예제보다 나쁜 것은 없습니다. 이런 상황을 방지하기 위해, 러스트는 당신의 문서에서 자동적으로 실행되는 예제를 지원합니다. 여기에 구체화돤 `src/lib.rs`가 예제와 함꼐 있습니다.

```rust,ignore
//! The `adder` crate provides functions that add numbers to other numbers.
//!
//! # Examples
//!
//! ```
//! assert_eq!(4, adder::add_two(2));
//! ```

/// This function adds two to its argument.
///
/// # Examples
///
/// ```
/// use adder::add_two;
///
/// assert_eq!(4, add_two(2));
/// ```
pub fn add_two(a: i32) -> i32 {
a + 2
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn it_works() {
assert_eq!(4, add_two(2));
}
}
```

모듈 레벨 문서인 `//!`와 함수 레벨 문서인 `///`를 주목하십시오. Rust의 문서 지원은 주석 안에 Markdown 문법을 사용하는 것을 지원하며, 따라서 세개의 역따옴표(grave)는 코드 블럭을 뜻합니다. 위의 코드와 같이, 그 아래에 예제를 붙여 `# Examples` 섹션을 첨가하는 것이 관습입니다.

테스트를 다시 한번 진행해 봅시다.

```bash
$ cargo test
Compiling adder v0.0.1 (file:///home/steve/tmp/adder)
Running target/adder-91b3e234d4ed382a

running 1 test
test tests::it_works ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured

Running target/lib-c18e7d3494509e74

running 1 test
test it_works ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured

Doc-tests adder

running 2 tests
test add_two_0 ... ok
test _0 ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured
```

이제 우리는 3종류의 테스트 전부를 다뤘습니다! 문서 테스트의 이름들을 확인해봅시다: `_0`는 모듈 테스트를 위해 생성된 것이고, `add_two_0`는 함수 테스트를 위한 것입니다. 여기에 붙은 순서는 `add_two_1`와 같이 당신이 예제를 붙여나감에 따라 자동적으로 증가할 것입니다.