Tổ chức Tests
Testing là một kỹ năng phức tạp. Nhiều lập trình viên sử dụng nhiều thuật ngữ khác nhau và tổ chức code tests khác nhau. Cộng đồng Rust đặt ra hai loại tests:
- Unit tests: nhỏ và tập trung vào một function, module độc lập tại một thời điểm.
- Integration tests: tests nằm ngoài thư viện của bạn, sử dụng thư viện của bạn để tests như thể là một chương trình thực tế sẽ thực sự sử dụng. Chỉ test trên public interface và tập trung vào cả 1 module cho một test.
Unit Tests
Unit tests được đặt trong thư mục src
trong mỗi file code mà bạn đang test.
Có một convention là tạo một module tên tests
trong mỗi file chứa function cần test,
annotate module này với attribute #[cfg(test)]
.
#[cfg(test)]
#[cfg(test)]
báo cho compiler biết module này được dùng để compile thành test, chỉ được dùng
khi chạy cargo test
, không dùng khi cargo build
.
File: src/lib.rs
#![allow(unused)] fn main() { pub fn adder(a: i32, b: i32) -> i32 { a + b } #[cfg(test)] mod tests { use super::*; #[test] fn test_adder() { let expected = 4; let actual = adder(2, 2); assert_eq!(expected, actual); } } }
Trong module tests
có thể sẽ có một vài helper function khác, do đó những function nào
là function test sẽ được đánh dấu là #[test]
để compiler nhận biết.
Test private function
Ở ví dụ trên thì public function adder
được import vào trong module tests
theo đúng rule của Rust.
Vậy còn private function thì sao? Có một cuộc tranh cãi trong cộng đồng về việc này.
Cuối cùng thì Rust cho phép import private function vào tests
module.
#![allow(unused)] fn main() { pub fn adder(a: i32, b: i32) -> i32 { adder_internal(a, b) } fn adder_internal(a: i32, b: i32) -> i32 { a + b } #[cfg(test)] mod tests { use super::*; #[test] fn test_adder() { let expected = 4; let actual = adder_internal(2, 2); assert_eq!(expected, actual); } } }
Integration Tests
Integration tests nằm hoàn toàn bên ngoài thư viện của bạn. Mục đích của integration tests nhằm kiểm tra các thành phần của thư viện của bạn hoạt động cùng với nhau có chính xác không.
Để bắt đầu viết integration tests, tạo thư mục tests
nằm cùng cấp với src
.
Trong thư mục tests
, cargo sẽ compile mỗi file thành một thành một crate độc lập.
File: tests/integration_test.rs
#![allow(unused)] fn main() { use adder; #[test] fưn it_adds_two() { assert_eq!(4, adder::adder(2, 2)); } }
Ở đây chúng ta import use adder
thay vì use crate::
do file integration test này là một crate độc lập.
Chạy cargo 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::test_adder ... 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đ
Do integration tests phải import thư viện để chạy test code, crate bắt buộc phải có src/lib.rs
.
Các binary crates không thể test theo cách này.
Do đó các project Rust họ thường tổ chức theo kiểu build binary từ src/main.rs
và import trực tiếp logic từ src/lib.rs
.
References
Doc Tests
Rust cũng hỗ trợ execute code ví dụ trên document như là một test. Đây là giải pháp cực kỳ thông minh giúp đảm bảo example code luôn up to date và nó có hoạt động.
#![allow(unused)] fn main() { /// Function that adding two number /// /// # Example /// /// ``` /// use adder::adder; /// /// assert_eq!(4, adder(2, 2)); /// ``` pub fn adder(a: i32, b: i32) -> i32 { a + b } }
Khi chạy cargo test
hoặc cargo test --doc
, cargo sẽ compile
phần example code này thành một crate test và thực thi nó.
Mặc định nếu không chỉ định ngôn ngữ cho block code thì rustdoc sẽ ngầm định nó là Rust code. Do đó
```
let x = 5;
```
sẽ tương đương với
```rust
let x = 5;
```
Ẩn một phần của example code
Đôi lúc bạn sẽ cần example code gọn hơn, ẩn bớt một số logic mà bạn chuẩn bị để code có thể chạy được, tránh làm distract của người xem.
/// ```
/// /// Some documentation.
/// # fn foo() {} // this function will be hidden
/// println!("Hello, World!");
/// ```
Chúng ta thêm #
ở phần đầu của dòng code muốn ẩn đi trong generate doc,
nó vẫn sẽ được compile như mình thường.
Sử dụng ?
trong doc tests
?
chỉ có thể được sử dụng khi function trả về Result<T, E>
. Hãy sử dụng cách sau:
/// A doc test using ?
///
/// ```
/// use std::io;
/// fn main() -> io::Result<()> {
/// let mut input = String::new();
/// io::stdin().read_line(&mut input)?;
/// Ok(())
/// }
/// ```
Cùng với việc sử dụng #
như ở trên, chúng ta có thể ẩn đi bớt logic.
/// A doc test using ?
///
/// ```
/// # use std::io;
/// # fn main() -> io::Result<()> {
/// let mut input = String::new();
/// io::stdin().read_line(&mut input)?;
/// # Ok(())
/// # }
/// ```