Trong quá trình phát triển ứng dụng, việc đảm bảo chất lượng phần mềm thông qua các bài kiểm thử là cực kỳ quan trọng. Khi cần kiểm tra cùng một logic nghiệp vụ với nhiều bộ dữ liệu đầu vào khác nhau, việc viết từng bài kiểm thử riêng lẻ có thể trở nên lặp lại và kém hiệu quả. Ví dụ, hãy xem xét một bài kiểm thử đơn giản sau:
mod tests {
#[test]
fn kiem_tra_tong_hai_so_co_dinh() {
let gia_tri_a = 1;
let gia_tri_b = 2;
assert_eq!(gia_tri_a + gia_tri_b, 3);
}
}
Nếu chúng ta cần kiểm tra phép cộng với nhiều cặp số khác, việc tạo ra một hàm test cho mỗi cặp sẽ rất tốn công. Một phương pháp không được khuyến khích là sử dụng vòng lặp bên trong một bài kiểm thử để xử lý nhiều trường hợp:
mod tests {
#[test]
fn kiem_tra_nhan_doi_voi_vong_lap() {
for so_nguyen in [1, 2, 3] {
assert_eq!(so_nguyen + so_nguyen, 2 * so_nguyen);
}
}
}
Vấn đề với cách tiếp cận này là toàn bộ vòng lặp chỉ được tính là một bài kiểm thử duy nhất. Nếu bất kỳ trường hợp nào trong vòng lặp thất bại, toàn bộ bài kiểm thử sẽ thất bại và các trường hợp tiếp theo trong vòng lặp có thể không được thực thi hoặc kết quả của chúng không được báo cáo riêng lẻ, gây khó khăn cho việc gỡ lỗi và phân tích kết quả.
Sử dụng Thư Viện Bên Thứ Ba cho Kiểm Thử Hướng Dữ Liệu
Để triển khai kiểm thử tham số hóa (parameterized testing) hoặc kiểm thử hướng dữ liệu (data-driven testing) trong Rust một cách hiệu quả, chúng ta có thể tận dụng các thư viện bên thứ ba mạnh mẽ hoặc tự định nghĩa macro. Dưới đây là một số thư viện phổ biến:
test-case: Cho phép bạn định nghĩa nhiều trường hợp kiểm thử với các tham số khác nhau bằng cách sử dụng các thuộc tính trên hàm test.rtest: Cung cấp tính năng tham số hóa và hỗ trợ các fixture tùy chỉnh.test-generator: Hỗ trợ tạo các bài kiểm thử dựa trên dữ liệu được định nghĩa trong các tệp.datatest: Cung cấp một cách tiếp cận ổn định để thực hiện kiểm thử hướng dữ liệu, bao gồm khả năng tạo dữ liệu test từ các tệp YAML.
Chúng ta sẽ sử dụng thư viện test-case làm ví dụ điển hình:
mod kiem_thu_phep_cong {
use test_case::test_case;
#[test_case(1, 1, 2; "Kiểm tra tổng 1 + 1")]
#[test_case(2, 3, 5; "Kiểm tra tổng 2 + 3")]
#[test_case(0, 0, 0; "Kiểm tra tổng 0 + 0")]
fn kiem_tra_ket_qua_cong(so_hang_thu_nhat: i32, so_hang_thu_hai: i32, tong_mong_doi: i32) {
assert_eq!(so_hang_thu_nhat + so_hang_thu_hai, tong_mong_doi);
}
}
Khi chạy các bài kiểm thử này bằng cargo test, kết quả sẽ hiển thị mỗi trường hợp tham số hóa như một bài kiểm thử độc lập:
running 3 tests
test kiem_thu_phep_cong::kiem_tra_ket_qua_cong::Kiểm_tra_tổng_0_0 ... ok
test kiem_thu_phep_cong::kiem_tra_ket_qua_cong::Kiểm_tra_tổng_1_1 ... ok
test kiem_thu_phep_cong::kiem_tra_ket_qua_cong::Kiểm_tra_tổng_2_3 ... ok
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Tự Định Nghĩa Macro Để Tạo Bài Kiểm Thử
Bên cạnh việc sử dụng các thư viện, bạn cũng có thể tự tạo macro để tự động sinh ra các bài kiểm thử từ một tập hợp dữ liệu. Phương pháp này mang lại sự linh hoạt cao và phù hợp với các yêu cầu tùy chỉnh.
macro_rules! tao_kiem_thu_phep_cong {
($ten_ham_test:ident, $tham_so_a:expr, $tham_so_b:expr, $ket_qua_mong_muon:expr) => {
#[test]
fn $ten_ham_test() {
assert_eq!($tham_so_a + $tham_so_b, $ket_qua_mong_muon);
}
};
}
mod cac_bai_kiem_thu {
tao_kiem_thu_phep_cong!(cong_mot_hai, 1, 2, 3);
tao_kiem_thu_phep_cong!(cong_hai_ba, 2, 3, 5);
tao_kiem_thu_phep_cong!(cong_khong_khong, 0, 0, 0);
}
Kiểm Thử Với Dữ Liệu Ngẫu Nhiên Qua Nhiều Vòng
Các phương pháp trên hiệu quả với các tập dữ liệu đã biết trước. Tuy nhiên, đôi khi chúng ta cần kiểm thử với dữ liệu ngẫu nhiên qua nhiều vòng để khám phá các trường hợp biên hoặc phát hiện lỗi mà dữ liệu cố định có thể bỏ qua. Để đạt được điều này, có thể kết hợp test-case, seq-macro và một macro tùy chỉnh.
macro_rules! tao_cac_kiem_thu_ngau_nhien {
($so_luong_lan:expr) => {
mod cac_test_ngau_nhien {
use rand::Rng;
use seq_macro::seq;
use test_case::test_case;
seq!(N in 0..$so_luong_lan {
#(#[test_case(N)])*
fn kiem_tra_tinh_chat_nhan_doi(lan_thu: usize) {
let mut bo_tao_so_ngau_nhien = rand::thread_rng();
let so_nguyen_ngau_nhien: i32 = bo_tao_so_ngau_nhien.gen_range(0..100);
// Kiểm tra tính chất: x * 2 == x + x
assert_eq!(so_nguyen_ngau_nhien * 2, so_nguyen_ngau_nhien + so_nguyen_ngau_nhien);
}
});
}
};
}
mod module_chinh_test {
tao_cac_kiem_thu_ngau_nhien!(5); // Thực hiện 5 lần kiểm thử với dữ liệu ngẫu nhiên
}
Để sử dụng các thư viện này, bạn cần thêm chúng vào phần [dev-dependencies] trong tệp Cargo.toml của dự án:
[dev-dependencies]
test-case = "2.2.2"
seq-macro = "0.3.5"
rand = "0.8.5"