예제가 아닌 속성을 테스트합니다. proptest가 임의의 입력을 생성하여 속성을 검증합니다.
use proptest::prelude::*;
// 테스트할 함수
fn reverse<T: Clone>(xs: &[T]) -> Vec<T> {
let mut rev = vec![];
for x in xs {
rev.insert(0, x.clone());
}
rev
}
// 속성 기반 테스트
proptest! {
#[test]
fn test_reverse_twice_is_identity(ref vec in any::<Vec<i32>>()) {
let reversed = reverse(vec);
let double_reversed = reverse(&reversed);
prop_assert_eq!(vec, &double_reversed);
}
#[test]
fn test_reverse_length(ref vec in any::<Vec<i32>>()) {
let reversed = reverse(vec);
prop_assert_eq!(vec.len(), reversed.len());
}
#[test]
fn test_string_concatenation(s1 in ".*", s2 in ".*") {
let combined = format!("{}{}", s1, s2);
prop_assert_eq!(combined.len(), s1.len() + s2.len());
}
}
// 범위 지정
proptest! {
#[test]
fn test_addition(a in 0..100i32, b in 0..100i32) {
let sum = a + b;
prop_assert!(sum >= a);
prop_assert!(sum >= b);
}
}
use proptest::prelude::*;
#[derive(Debug, Clone)]
struct Email {
local: String,
domain: String,
}
impl Email {
fn to_string(&self) -> String {
format!("{}@{}", self.local, self.domain)
}
}
// 이메일 생성 전략
fn email_strategy() -> impl Strategy<Value = Email> {
("[a-z]{3,10}", "[a-z]{3,10}\\.[a-z]{2,3}")
.prop_map(|(local, domain)| Email { local, domain })
}
proptest! {
#[test]
fn test_email_format(email in email_strategy()) {
let email_str = email.to_string();
prop_assert!(email_str.contains('@'));
prop_assert!(email_str.contains('.'));
}
}
정확하고 재현 가능한 벤치마크를 작성합니다.
// Cargo.toml
// [dev-dependencies]
// criterion = "0.5"
// [[bench]]
// name = "my_benchmark"
// harness = false
use criterion::{black_box, criterion_group, criterion_main, Criterion, BenchmarkId};
fn fibonacci_recursive(n: u64) -> u64 {
match n {
0 => 0,
1 => 1,
n => fibonacci_recursive(n - 1) + fibonacci_recursive(n - 2),
}
}
fn fibonacci_iterative(n: u64) -> u64 {
let mut a = 0;
let mut b = 1;
for _ in 0..n {
let temp = a;
a = b;
b = temp + b;
}
a
}
fn bench_fibonacci(c: &mut Criterion) {
let mut group = c.benchmark_group("fibonacci");
for i in [10u64, 15, 20].iter() {
group.bench_with_input(BenchmarkId::new("recursive", i), i, |b, i| {
b.iter(|| fibonacci_recursive(black_box(*i)));
});
group.bench_with_input(BenchmarkId::new("iterative", i), i, |b, i| {
b.iter(|| fibonacci_iterative(black_box(*i)));
});
}
group.finish();
}
criterion_group!(benches, bench_fibonacci);
criterion_main!(benches);
use criterion::{black_box, criterion_group, criterion_main, Criterion, BenchmarkId};
fn bench_vector_operations(c: &mut Criterion) {
let mut group = c.benchmark_group("vector_ops");
for size in [100, 1000, 10000].iter() {
group.bench_with_input(BenchmarkId::new("push", size), size, |b, &size| {
b.iter(|| {
let mut vec = Vec::new();
for i in 0..size {
vec.push(black_box(i));
}
vec
});
});
group.bench_with_input(BenchmarkId::new("with_capacity", size), size, |b, &size| {
b.iter(|| {
let mut vec = Vec::with_capacity(size);
for i in 0..size {
vec.push(black_box(i));
}
vec
});
});
}
group.finish();
}
criterion_group!(benches, bench_vector_operations);
criterion_main!(benches);
// tests/integration_test.rs
// 공통 설정 모듈
mod common;
use mylib::Database;
#[test]
fn test_database_crud() {
let db = common::setup_database();
// Create
db.insert("key1", "value1").unwrap();
// Read
assert_eq!(db.get("key1").unwrap(), Some("value1"));
// Update
db.update("key1", "new_value").unwrap();
assert_eq!(db.get("key1").unwrap(), Some("new_value"));
// Delete
db.delete("key1").unwrap();
assert_eq!(db.get("key1").unwrap(), None);
common::teardown_database(db);
}
// tests/common/mod.rs
use mylib::Database;
pub fn setup_database() -> Database {
Database::new_in_memory()
}
pub fn teardown_database(_db: Database) {
// 정리 작업
}
// tests/fixtures/mod.rs
pub struct TestContext {
pub temp_dir: tempfile::TempDir,
pub config: Config,
}
impl TestContext {
pub fn new() -> Self {
let temp_dir = tempfile::tempdir().unwrap();
let config = Config::default();
TestContext { temp_dir, config }
}
pub fn create_test_file(&self, name: &str, content: &str) -> PathBuf {
let path = self.temp_dir.path().join(name);
std::fs::write(&path, content).unwrap();
path
}
}
impl Drop for TestContext {
fn drop(&mut self) {
// 자동으로 임시 디렉토리 정리
}
}
// 사용
#[test]
fn test_with_fixture() {
let ctx = TestContext::new();
let file = ctx.create_test_file("test.txt", "Hello, World!");
// 테스트 수행
assert!(file.exists());
}
use mockall::*;
#[automock]
trait Database {
fn get(&self, key: &str) -> Option<String>;
fn set(&mut self, key: &str, value: String) -> Result<(), String>;
}
struct UserService<D: Database> {
db: D,
}
impl<D: Database> UserService<D> {
fn get_user(&self, id: &str) -> Option<String> {
self.db.get(id)
}
fn create_user(&mut self, id: &str, name: String) -> Result<(), String> {
self.db.set(id, name)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_user() {
let mut mock_db = MockDatabase::new();
mock_db.expect_get()
.with(eq("user123"))
.times(1)
.returning(|_| Some("Alice".to_string()));
let service = UserService { db: mock_db };
assert_eq!(service.get_user("user123"), Some("Alice".to_string()));
}
#[test]
fn test_create_user() {
let mut mock_db = MockDatabase::new();
mock_db.expect_set()
.with(eq("user456"), eq("Bob".to_string()))
.times(1)
.returning(|_, _| Ok(()));
let mut service = UserService { db: mock_db };
assert!(service.create_user("user456", "Bob".to_string()).is_ok());
}
}
문서의 예제 코드가 실제로 작동하는지 검증합니다.
/// 두 숫자를 더합니다.
///
/// # Examples
///
/// ```
/// use mylib::add;
///
/// let result = add(2, 3);
/// assert_eq!(result, 5);
/// ```
///
/// # Panics
///
/// 오버플로우가 발생하면 패닉합니다.
///
/// ```should_panic
/// use mylib::add;
///
/// let result = add(i32::MAX, 1);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
a.checked_add(b).expect("overflow")
}
/// 복잡한 예제
///
/// ```
/// use mylib::Calculator;
///
/// let mut calc = Calculator::new();
/// calc.add(5);
/// calc.multiply(2);
/// assert_eq!(calc.result(), 10);
/// ```
///
/// # Hidden setup code
///
/// ```
/// # use mylib::Calculator;
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let calc = Calculator::new();
/// println!("Calculator ready");
/// # Ok(())
/// # }
/// ```
pub struct Calculator {
value: i32,
}
$ cargo install cargo-tarpaulin # 커버리지 측정 $ cargo tarpaulin --out Html # 특정 패키지만 $ cargo tarpaulin --packages mylib # 특정 테스트만 $ cargo tarpaulin --test integration_tests
$ rustup component add llvm-tools-preview $ cargo install cargo-llvm-cov # 커버리지 측정 $ cargo llvm-cov # HTML 리포트 $ cargo llvm-cov --html # 특정 테스트만 $ cargo llvm-cov --test integration_tests
// src/lib.rs
pub struct Calculator {
value: i32,
}
impl Calculator {
pub fn new() -> Self {
Calculator { value: 0 }
}
pub fn add(&mut self, n: i32) {
self.value += n;
}
pub fn multiply(&mut self, n: i32) {
self.value *= n;
}
pub fn result(&self) -> i32 {
self.value
}
}
// tests/calculator_test.rs
use mylib::Calculator;
use proptest::prelude::*;
#[test]
fn test_basic_operations() {
let mut calc = Calculator::new();
calc.add(5);
assert_eq!(calc.result(), 5);
calc.multiply(2);
assert_eq!(calc.result(), 10);
}
proptest! {
#[test]
fn test_add_is_commutative(a in any::<i16>(), b in any::<i16>()) {
let mut calc1 = Calculator::new();
calc1.add(a as i32);
calc1.add(b as i32);
let mut calc2 = Calculator::new();
calc2.add(b as i32);
calc2.add(a as i32);
prop_assert_eq!(calc1.result(), calc2.result());
}
}
// benches/calculator_bench.rs
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use mylib::Calculator;
fn bench_operations(c: &mut Criterion) {
c.bench_function("calculator_operations", |b| {
b.iter(|| {
let mut calc = Calculator::new();
calc.add(black_box(5));
calc.multiply(black_box(10));
calc.result()
});
});
}
criterion_group!(benches, bench_operations);
criterion_main!(benches);