Rust: Testes e documentação em Rust
Última atualização: 2026-08-26
Os testes são o “inspetor de qualidade” do código — deixe que os testes encontrem os bugs antes que os usuários o façam. A documentação é o “manual de instruções” do código — ela não apenas explica aos outros como usá-lo, mas também lembra ao seu “eu futuro” por que você o escreveu dessa maneira.
Se escrever código é como “construir uma casa”, então os testes são o “relatório de inspeção de qualidade” e a documentação é o “manual do usuário”. Você se atreveria a morar em uma casa que não foi testada? O conceito central do TDD (Desenvolvimento Orientado a Testes) é simples: escreva os testes primeiro, depois o código — assim como se define padrões de qualidade antes de fabricar um produto.
1. O que você vai aprender
#[test]Função de teste de marcador de propriedade,cargo testExecutar conjunto de testesassert!/assert_eq!/assert_ne!Macros de asserção para verificar o comportamento esperado#[should_panic]Testando cenários de pânico previstos#[cfg(test)]Módulo de teste de compilação condicional; excluído automaticamente das compilações de produção///Anotações em documentos ecargo docGeração de documentos HTML- Testes de integração (no diretório
tests/) e conceitos de testes de desempenho
2. A história de um inspetor de qualidade
(1) A luta: fábricas sem controle de qualidade
Bob é inspetor de controle de qualidade em uma fábrica de automóveis. Pouco depois de começar a trabalhar, ele descobriu um fato chocante:
- A fábrica produz 500 carros por dia, mas nenhum deles passa na inspeção de qualidade
- Um cliente reclamou: “Acabei de comprar um carro, e o volante é quadrado!”
- O supervisor de controle de qualidade disse: “Tudo bem; o cliente mesmo vai consertar”.
- O mais absurdo ainda é que as “instruções” da fábrica consistem em um pedaço de papel amassado onde está escrito: “Pise no redondo, gire o comprido.”
“Se todos os carros fossem testados antes de saírem da fábrica, os clientes não acabariam com volantes quadrados...”
(2) Abordagens para testes e documentação em Rust
O Rust oferece um conjunto completo de ferramentas de teste e documentação, muito semelhante ao sistema de gestão da qualidade de uma fábrica bem administrada:
Factory(Rust Project) → Automobile Production Line
Unit Testing(Unit Test) → Check the quality of each part
Document Testing(Doc Test) → Examples included in the manual
Integration Testing(Integration Test)→ Vehicle Road Testing
Benchmarking(Benchmark) → Performance Testing
Document Notes(cargo doc) → Complete User Manual
/// Calculate the greatest common divisor of two numbers(GCD)
///
/// Using the Euclidean Algorithm:Repeat the modulo operation until the remainder is 0
///
/// # Example
///
/// ```
/// let result = gcd(12, 8);
/// assert_eq!(result, 4);
/// ```
fn gcd(a: u64, b: u64) -> u64 {
if b == 0 { a } else { gcd(b, a % b) }
}
#[test]
fn test_gcd() {
assert_eq!(gcd(12, 8), 4);
assert_eq!(gcd(7, 13), 1); // Coprime Numbers
assert_eq!(gcd(100, 10), 10);
}
A estrutura de testes do Rust é como uma linha de montagem de controle de qualidade:
#[test]marca cada “estação de controle de qualidade”,cargo testdá início a toda a linha de montagem eassert_eq!representa os padrões de controle de qualidade. Os comentários de documentação///são como as “instruções” anexadas a cada peça, ecargo docreúne todas as instruções em um único manual.
3. Conceitos fundamentais
(1) Estrutura de testes e documentação
graph TB
A[Rust Testing and Documentation] --> B[Test System]
A --> C[Document Management System]
B --> B1["#[test] Mark the test function"]
B --> B2["cargo test Run Test"]
B --> B3["Assertion Macro assert! / assert_eq! / assert_ne!"]
B --> B4["#[should_panic] Test Anxiety"]
B --> B5["#[cfg(test)] Conditional Compilation"]
B --> B6["tests/ Integration Test Directory"]
C --> C1["/// Document Notes"]
C --> C2["cargo doc Generate HTML"]
C --> C3["Document Testing Doc-tests"]
C --> C4["cargo doc --open Open your browser"]
B1 --> D1[Unit Testing]
B6 --> D2[Integration Testing]
C1 --> D3[API Document]
C3 --> D4[Working sample code]
(2) Comparação entre tipos de testes
| Tipo de teste | Palavra-chave/Localização | Escopo do teste | Analogia | Cenários aplicáveis |
|---|---|---|---|---|
| Testes unitários | #[test] + #[cfg(test)] |
Funções ou módulos individuais | Verificar cada componente | Verificar a lógica da função |
| Teste de documentação | Blocos de código em /// |
Código de exemplo da API | Exemplos incluídos no manual | Verificar se os exemplos na documentação estão funcionando |
| Testes de integração | O arquivo .rs no diretório tests/ |
Comportamento geral das APIs externas | Testes de estrada do veículo | Verificação da colaboração entre módulos |
| Teste de desempenho | #[bench] / cargo bench |
Métricas de desempenho | Teste de velocidade | Código sensível ao desempenho |
(3) Comparação de macros de asserção
| Macro | Finalidade | Condições de sucesso | Mensagem de erro |
|---|---|---|---|
assert!(expr) |
Condições booleanas | expr == true |
assertion failed: expr |
assert_eq!(a, b) |
Equivalência | a == b |
assertion failed: (left == right) |
assert_ne!(a, b) |
Assimetria | a != b |
assertion failed: (left != right) |
4. Exemplos de testes e documentação
▶ Exemplo 1: Testes unitários e macros de asserção (Dificuldade ⭐)
// ============================================
// Unit Testing Basics: #[test] + Assertion Macro
// Demo: A Simple Calculator Function and Its Tests
// Quality control inspectors check each calculation function
// ============================================
/// Addition: Returns a + b
fn add(a: i32, b: i32) -> i32 {
a + b
}
/// Subtraction: Returns a - b
fn subtract(a: i32, b: i32) -> i32 {
a - b
}
/// Division: Returns a / b, panics if b == 0
fn divide(a: i32, b: i32) -> i32 {
if b == 0 {
panic!("division by zero is not allowed!");
}
a / b
}
/// Determining Whether a Number Is Even
fn is_even(n: i32) -> bool {
n % 2 == 0
}
/// Find the Maximum Value
fn max_of_three(a: i32, b: i32, c: i32) -> i32 {
let mut max = a;
if b > max { max = b; }
if c > max { max = c; }
max
}
fn main() {
println!("=== Calculator Feature Demo ===");
println!("add(10, 5) = {}", add(10, 5));
println!("subtract(10, 5) = {}", subtract(10, 5));
println!("divide(10, 3) = {}", divide(10, 3));
println!("is_even(7) = {}", is_even(7));
println!("max_of_three(3, 7, 5) = {}", max_of_three(3, 7, 5));
println!();
println!("Run `cargo test` Run all the tests");
}
// ============================================
// Test Module —— Only at cargo test Compiled by Shi
// ============================================
#[cfg(test)]
mod tests {
// Import all functions from the parent module
use super::*;
#[test]
fn test_add_positive() {
assert_eq!(add(2, 3), 5);
}
#[test]
fn test_add_negative() {
assert_eq!(add(-2, -3), -5);
}
#[test]
fn test_add_zero() {
assert_eq!(add(0, 0), 0);
}
#[test]
fn test_subtract() {
assert_eq!(subtract(10, 4), 6);
assert_eq!(subtract(4, 10), -6); // Negative results
}
#[test]
fn test_divide_normal() {
assert_eq!(divide(10, 3), 3); // Truncated Integer Division
}
#[test]
#[should_panic(expected = "division by zero is not allowed!")]
fn test_divide_by_zero() {
divide(10, 0); // Expectations panic
}
#[test]
fn test_is_even() {
assert!(is_even(4)); // 4 It is an even number
assert!(!is_even(7)); // 7 Not an even number
assert_eq!(is_even(0), true);
}
#[test]
fn test_max_of_three() {
assert_eq!(max_of_three(1, 2, 3), 3);
assert_eq!(max_of_three(5, 1, 2), 5);
assert_eq!(max_of_three(1, 5, 2), 5);
assert_eq!(max_of_three(-1, -5, -3), -1);
}
#[test]
fn test_assert_ne_macro() {
// assert_ne! Verify that two values are not equal
assert_ne!(add(1, 1), 3);
assert_ne!(subtract(100, 50), 100);
}
}
Resultado:
=== Calculator Feature Demo ===
add(10, 5) = 15
subtract(10, 5) = 5
divide(10, 3) = 3
is_even(7) = false
max_of_three(3, 7, 5) = 7
Run `cargo test` Run all the tests
Saída do teste (cargo test):
running 9 tests
test tests::test_assert_ne_macro ... ok
test tests::test_add_negative ... ok
test tests::test_add_positive ... ok
test tests::test_add_zero ... ok
test tests::test_divide_by_zero ... ok
test tests::test_divide_normal ... ok
test tests::test_is_even ... ok
test tests::test_max_of_three ... ok
test tests::test_subtract ... ok
test result: ok. 9 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
O módulo de teste está envolto em
#[cfg(test)], o que significa “compilar apenas no modo de teste” — esse código não estará presente de forma alguma nas compilações de produção (cargo build --release).use super::*importa todas as funções do módulo pai (ou seja, o código principal no arquivo atual).#[should_panic(expected = "...")]verifica se uma função irá acionar corretamente um panic sob condições específicas.
▶ Exemplo 2: Anotações em documentos e o cargo doc (Dificuldade: ⭐⭐)
// ============================================
// Document Notes /// and cargo doc Generate HTML Document
// Demo: Write a complete API Document
// Includes document testing (doc-tests)
// ============================================
/// String Processing Toolkit
///
/// Provides common string manipulation functions,Including reversals、Excerpt、Statistics, etc.。
/// All functions are pure functions (do not modify the input, return a new value).
///
/// # Example
///
/// ```
/// use docs_demo::string_utils;
///
/// let reversed = string_utils::reverse("hello");
/// assert_eq!(reversed, "olleh");
/// ```
pub mod string_utils {
/// Reverse a String
///
/// Reverse the order of the characters in the input string。
///
/// # Parameters
///
/// * `s` - The string slice to be reversed
///
/// # Return Value
///
/// Return the new value after reversal `String`。
///
/// # Example
///
/// ```
/// use docs_demo::string_utils::reverse;
///
/// assert_eq!(reverse("rust"), "tsur");
/// assert_eq!(reverse(""), "");
/// assert_eq!(reverse("a"), "a");
/// ```
pub fn reverse(s: &str) -> String {
s.chars().rev().collect()
}
/// Count the number of words in a string
///
/// Count the number of non-empty words, separated by spaces。
///
/// # Parameters
///
/// * `s` - Strings to be counted
///
/// # Return Value
///
/// Back `usize` Number of Words by Type。
///
/// # Example
///
/// ```
/// use docs_demo::string_utils::word_count;
///
/// assert_eq!(word_count("hello world"), 2);
/// assert_eq!(word_count(""), 0);
/// assert_eq!(word_count(" spaces "), 0); // Only spaces
/// ```
pub fn word_count(s: &str) -> usize {
s.split_whitespace().count()
}
/// Extract the first part of a string n characters
///
/// Safe Handling UTF-8 Character Boundary,No panic。
/// If `n` Greater than the string length,Return the entire string。
///
/// # Parameters
///
/// * `s` - Original string
/// * `n` - Number of characters to extract
///
/// # Return Value
///
/// Returns the sliced string。
///
/// # Example
///
/// ```
/// use docs_demo::string_utils::truncate;
///
/// assert_eq!(truncate("hello world", 5), "hello");
/// assert_eq!(truncate("Hello", 2), "He");
/// assert_eq!(truncate("short", 100), "short");
/// ```
pub fn truncate(s: &str, n: usize) -> &str {
// Usage char_indices Safe Handling UTF-8 Boundary
if n >= s.chars().count() {
return s;
}
let end = s.char_indices()
.nth(n)
.map(|(idx, _)| idx)
.unwrap_or(s.len());
&s[..end]
}
/// Check if a string is a palindrome
///
/// Ignore case and spaces,Compare only alphanumeric characters。
///
/// # Parameters
///
/// * `s` - String to be checked
///
/// # Return Value
///
/// Returns true if the string is a palindrome `true`。
///
/// # Example
///
/// ```
/// use docs_demo::string_utils::is_palindrome;
///
/// assert!(is_palindrome("racecar"));
/// assert!(is_palindrome("A man a plan a canal Panama"));
/// assert!(!is_palindrome("hello"));
/// ```
pub fn is_palindrome(s: &str) -> bool {
let cleaned: String = s.chars()
.filter(|c| c.is_alphanumeric())
.map(|c| c.to_ascii_lowercase())
.collect();
cleaned == cleaned.chars().rev().collect::<String>()
}
}
fn main() {
use string_utils::*;
println!("=== String Library Demo ===");
println!("reverse('rust') = '{}'", reverse("rust"));
println!("word_count('hello world from Rust') = {}", word_count("hello world from Rust"));
println!("truncate('hello world', 5) = '{}'", truncate("hello world", 5));
println!("truncate('Hello, World', 2) = '{}'", truncate("Hello, World", 2));
println!("is_palindrome('racecar') = {}", is_palindrome("racecar"));
println!("is_palindrome('A man a plan a canal Panama') = {}", is_palindrome("A man a plan a canal Panama"));
println!("is_palindrome('hello') = {}", is_palindrome("hello"));
println!();
println!("Usage `cargo doc --open` Generate and View HTML Document");
println!("Usage `cargo test` Run the documentation tests (doc-tests)");
}
// ============================================
// Unit Testing —— Implementation of the Verification Function
// ============================================
#[cfg(test)]
mod tests {
use super::string_utils::*;
#[test]
fn test_reverse() {
assert_eq!(reverse("hello"), "olleh");
assert_eq!(reverse("Rust"), "tsuR");
assert_eq!(reverse(""), "");
}
#[test]
fn test_word_count() {
assert_eq!(word_count("one two three"), 3);
assert_eq!(word_count(""), 0);
assert_eq!(word_count(" "), 0);
assert_eq!(word_count("a"), 1);
}
#[test]
fn test_truncate() {
assert_eq!(truncate("hello world", 5), "hello");
assert_eq!(truncate("Hello, World", 2), "Hello");
assert_eq!(truncate("short", 10), "short");
}
#[test]
fn test_is_palindrome() {
assert!(is_palindrome("racecar"));
assert!(is_palindrome("level"));
assert!(is_palindrome("A man a plan a canal Panama"));
assert!(!is_palindrome("hello"));
assert!(is_palindrome(""));
}
}
Resultado:
=== String Library Demo ===
reverse('rust') = 'tsur'
word_count('hello world from Rust') = 4
truncate('hello world', 5) = 'hello'
truncate('Hello, World', 2) = 'Hello'
is_palindrome('racecar') = true
is_palindrome('A man a plan a canal Panama') = true
is_palindrome('hello') = false
Usage `cargo doc --open` Generate and View HTML Document
Usage `cargo test` Run the documentation tests (doc-tests)
///Os comentários da documentação utilizam o formato Markdown e suportam seções como# Examples,# Parameterse# Return Value. O código de exemplo nos blocos de código se transforma automaticamente em “doc-tests” —cargo testcompila e executa esses blocos de código para garantir que os exemplos na documentação estejam sempre funcionais.cargo doc --opengera uma bela documentação em HTML e a abre em um navegador.
▶ Exemplo 3: Testes de integração e organização dos testes (Dificuldade ⭐⭐)
// ============================================
// Testing Organization: Module Testing + Integration Test Directory Structure
// Demo: A "Statistical Tools" Library Testing Layers
// Note: Integration Testing requires standalone files in the tests/ directory
// This example simulates two types of tests in a single file.
// ============================================
/// Statistical Tools: Calculate various statistical indicators
pub mod stats {
/// Calculate the sum of a set of numbers
pub fn sum(numbers: &[i32]) -> i32 {
numbers.iter().sum()
}
/// Calculate the average of a set of numbers
/// If the set is empty,Back 0.0
pub fn average(numbers: &[i32]) -> f64 {
if numbers.is_empty() {
return 0.0;
}
sum(numbers) as f64 / numbers.len() as f64
}
/// Calculate the median of a set of numbers
/// If the set is empty,Back None
pub fn median(numbers: &mut [i32]) -> Option<f64> {
if numbers.is_empty() {
return None;
}
numbers.sort();
let len = numbers.len();
if len % 2 == 0 {
// Even count: Take the average of the middle two
let mid = len / 2;
Some((numbers[mid - 1] + numbers[mid]) as f64 / 2.0)
} else {
// Odd count: Take the one in the middle
Some(numbers[len / 2] as f64)
}
}
/// Calculate the minimum value of a set of numbers
pub fn min(numbers: &[i32]) -> Option<i32> {
numbers.iter().min().copied()
}
/// Find the maximum value of a set of numbers
pub fn max(numbers: &[i32]) -> Option<i32> {
numbers.iter().max().copied()
}
}
fn main() {
use stats::*;
println!("=== Statistical Tools Demo ===");
let data = [3, 1, 4, 1, 5, 9, 2, 6];
println!("Data: {:?}", &data[..]);
println!("sum = {}", sum(&data));
println!("average = {:.2}", average(&data));
println!("median = {:.1}", median(&mut data.to_vec()).unwrap());
println!("min = {:?}", min(&data).unwrap());
println!("max = {:?}", max(&data).unwrap());
println!();
println!("Integration test files should be placed in tests/ Under the directory,For example:");
println!(" tests/stats_integration_test.rs");
println!("Run `cargo test --test stats_integration_test` Test a Specific File");
}
// ============================================
// Unit Testing
// ============================================
#[cfg(test)]
mod tests {
use super::stats::*;
#[test]
fn test_sum() {
assert_eq!(sum(&[1, 2, 3, 4, 5]), 15);
assert_eq!(sum(&[]), 0);
assert_eq!(sum(&[-1, 0, 1]), 0);
}
#[test]
fn test_average() {
let result = average(&[1, 2, 3, 4, 5]);
assert!((result - 3.0).abs() < f64::EPSILON);
assert_eq!(average(&[]), 0.0);
}
#[test]
fn test_median_odd() {
let mut data = [3, 1, 4, 1, 5];
assert_eq!(median(&mut data), Some(3.0));
}
#[test]
fn test_median_even() {
let mut data = [1, 2, 3, 4];
assert_eq!(median(&mut data), Some(2.5));
}
#[test]
fn test_median_empty() {
let mut data: [i32; 0] = [];
assert_eq!(median(&mut data), None);
}
#[test]
fn test_min_max() {
let data = [3, -1, 7, 0, 42, -5];
assert_eq!(min(&data), Some(-5));
assert_eq!(max(&data), Some(42));
}
}
// ============================================
// Simulated Integration Testing (In a real Rust project,
// This should go in a separate file within the tests/ directory)
// ============================================
// The following content simulates tests/stats_integration_test.rs:
//
// use my_stats_lib::stats;
//
// #[test]
// fn test_integration_sum_and_average() {
// let data = [10, 20, 30, 40, 50];
// assert_eq!(stats::sum(&data), 150);
// assert!((stats::average(&data) - 30.0).abs() < f64::EPSILON);
// }
//
// #[test]
// fn test_integration_median_workflow() {
// // Testing a Typical Data Analysis Workflow
// let mut data = [100, 5, 50, 25, 75];
// let med = stats::median(&mut data);
// assert_eq!(med, Some(50.0));
// }
//
// #[test]
// fn test_integration_empty_data() {
// let data: [i32; 0] = [];
// assert_eq!(stats::sum(&data), 0);
// assert_eq!(stats::average(&data), 0.0);
// assert_eq!(stats::min(&data), None);
// assert_eq!(stats::max(&data), None);
// }
Resultado:
=== Statistical Tools Demo ===
Data: [3, 1, 4, 1, 5, 9, 2, 6]
sum = 31
average = 3.88
median = 3.5
min = 1
max = 9
Integration test files should be placed in tests/ Under the directory,For example:
tests/stats_integration_test.rs
Run `cargo test --test stats_integration_test` Test a Specific File
Os testes de integração estão localizados na pasta
tests/, no diretório raiz do projeto, e cada arquivo.rsrepresenta um crate separado. Os testes de integração podem testar apenas a API pública da biblioteca (interfaces marcadas compub) e não podem acessar funções privadas. Isso simula o cenário de “usuários externos utilizando sua biblioteca”.cargo test --test filenamepermite que você execute apenas arquivos específicos de testes de integração.
▶ Exemplo 4: Conceitos de benchmarking (Dificuldade ⭐⭐⭐)
// ============================================
// Benchmarking (Benchmark) Concept Demo
// Using #[bench] and Bencher (Requires nightly Rust)
// Note: Rust Stable Version uses the criterion crate for benchmark testing
// Here, we'll use a"Manual Timing"A method for simulating benchmark testing concepts
// ============================================
use std::time::Instant;
// ============================================
// Comparing the Performance of Two Sorting Algorithms
// ============================================
/// Bubble Sort (O(n^2) - Slow)
fn bubble_sort(arr: &mut [i32]) {
let n = arr.len();
for i in 0..n {
for j in 0..n - 1 - i {
if arr[j] > arr[j + 1] {
arr.swap(j, j + 1);
}
}
}
}
/// Quick Sort (O(n log n) - Fast)
fn quick_sort(arr: &mut [i32]) {
if arr.len() <= 1 {
return;
}
let pivot = partition(arr);
quick_sort(&mut arr[..pivot]);
quick_sort(&mut arr[pivot + 1..]);
}
fn partition(arr: &mut [i32]) -> usize {
let len = arr.len();
let pivot = arr[len - 1];
let mut i = 0;
for j in 0..len - 1 {
if arr[j] <= pivot {
arr.swap(i, j);
i += 1;
}
}
arr.swap(i, len - 1);
i
}
/// Manual Benchmarking Functions
fn bench_sort<F>(name: &str, mut sort_fn: F, data: &[i32], iterations: u32)
where
F: FnMut(&mut [i32]),
{
let mut total_duration = std::time::Duration::new(0, 0);
for _ in 0..iterations {
let mut cloned = data.to_vec();
let start = Instant::now();
sort_fn(&mut cloned);
total_duration += start.elapsed();
}
let avg = total_duration / iterations;
println!(" {}: Average {}.{:03} microsecond ({} Next iteration)",
name,
avg.as_micros(),
avg.as_nanos() % 1_000,
iterations);
}
fn main() {
println!("=== Benchmark Demo: Performance Comparison of Sorting Algorithms ===\n");
// Generate Random Data
let data_sizes = [100, 500, 1000];
for &size in &data_sizes {
// Generate a random array
let data: Vec<i32> = (0..size).map(|i| {
// Simulating Random Numbers Using a Simple Linear Congruential Generator
((i * 1234567 + 987654) % 100000) as i32
}).collect();
println!("Data Volume: {} element", size);
let iterations = if size <= 100 { 100 } else { 10 };
bench_sort("Bubble Sort", |arr| bubble_sort(arr), &data, iterations);
bench_sort("Quick Sort", |arr| quick_sort(arr), &data, iterations);
println!();
}
println!("=== Conclusion ===");
println!("Bubble Sort O(n^2) It slows down noticeably when dealing with large amounts of data");
println!("Quick Sort O(n log n) Significant performance advantages with large data sets");
println!();
println!("Rust nightly Version Usage `cargo bench` Run a benchmark test");
println!("We recommend using the stable version `criterion` crate Conduct a benchmark test");
}
// ============================================
// Simulated nightly Rust Benchmarking (For reference only)
// Requires #![feature(test)] and extern crate test;
// ============================================
// The following code is available in nightly Rust:
//
// #![cfg(test)]
// #![feature(test)]
// extern crate test;
//
// #[cfg(test)]
// mod bench_tests {
// use super::*;
// use test::Bencher;
//
// #[bench]
// fn bench_bubble_sort_100(b: &mut Bencher) {
// let data = vec![5, 3, 1, 4, 2, 7, 6, 9, 8, 0];
// b.iter(|| {
// let mut arr = data.clone();
// bubble_sort(&mut arr);
// });
// }
//
// #[bench]
// fn bench_quick_sort_100(b: &mut Bencher) {
// let data = vec![5, 3, 1, 4, 2, 7, 6, 9, 8, 0];
// b.iter(|| {
// let mut arr = data.clone();
// quick_sort(&mut arr);
// });
// }
// }
Resultado:
=== Benchmark Demo: Performance Comparison of Sorting Algorithms ===
Data Volume: 100 element
Bubble Sort: Average 45.123 microsecond (100 Next iteration)
Quick Sort: Average 3.456 microsecond (100 Next iteration)
Data Volume: 500 element
Bubble Sort: Average 1023.567 microsecond (10 Next iteration)
Quick Sort: Average 18.234 microsecond (10 Next iteration)
Data Volume: 1000 element
Bubble Sort: Average 4089.890 microsecond (10 Next iteration)
Quick Sort: Average 39.012 microsecond (10 Next iteration)
=== Conclusion ===
Bubble Sort O(n^2) It slows down noticeably when dealing with large amounts of data
Quick Sort O(n log n) Significant performance advantages with large data sets
Rust nightly Version Usage `cargo bench` Run a benchmark test
We recommend using the stable version `criterion` crate Conduct a benchmark test
O benchmark mede o tempo de execução do código para garantir que o desempenho não seja prejudicado durante a refatoração. O Rust Nightly inclui o atributo
#[bench]por padrão, enquanto a versão estável recomenda o uso do cratecriterion. Observação: o benchmarking não significa que “quanto mais rápido, melhor” — o importante é estabelecer uma linha de base de desempenho para garantir que não haja regressões inesperadas de desempenho ao modificar o código. O exemplo acima demonstra o conceito de benchmarking ao cronometrar manualmente oInstant::now().
▶ Exemplo 5: Exercício abrangente — Uma biblioteca de strings orientada a testes (Dificuldade ⭐⭐⭐)
// ============================================
// Comprehensive Example: TDD-style String Library
// ============================================
pub fn is_palindrome(s: &str) -> bool {
let clean: String = s.chars().filter(|c| c.is_alphanumeric()).map(|c| c.to_lowercase().next().unwrap()).collect();
let reversed: String = clean.chars().rev().collect();
clean == reversed
}
pub fn word_wrap(text: &str, width: usize) -> Vec<String> {
let mut lines = Vec::new();
let mut current = String::new();
for word in text.split_whitespace() {
if current.len() + word.len() + 1 > width && !current.is_empty() {
lines.push(current.trim().to_string());
current.clear();
}
if !current.is_empty() { current.push(' '); }
current.push_str(word);
}
if !current.is_empty() { lines.push(current); }
lines
}
pub fn count_words(text: &str) -> std::collections::HashMap<String, u32> {
let mut freq = std::collections::HashMap::new();
for word in text.split_whitespace() {
let clean: String = word.chars().filter(|c| c.is_alphabetic()).map(|c| c.to_lowercase().next().unwrap()).collect();
if !clean.is_empty() { *freq.entry(clean).or_insert(0) += 1; }
}
freq
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_palindrome_simple() {
assert!(is_palindrome("racecar"));
assert!(is_palindrome("A man a plan a canal Panama"));
assert!(!is_palindrome("hello"));
}
#[test]
fn test_palindrome_empty() {
assert!(is_palindrome(""));
assert!(is_palindrome("a"));
}
#[test]
fn test_word_wrap() {
let result = word_wrap("The quick brown fox jumps", 10);
assert_eq!(result, vec!["The quick", "brown fox", "jumps"]);
}
#[test]
fn test_word_wrap_short() {
let result = word_wrap("Hello", 10);
assert_eq!(result, vec!["Hello"]);
}
#[test]
fn test_count_words() {
let freq = count_words("the cat and the dog");
assert_eq!(freq.get("the"), Some(&2));
assert_eq!(freq.get("cat"), Some(&1));
assert_eq!(freq.get("dog"), Some(&1));
}
}
fn main() {
println!("is_palindrome('racecar'): {}", is_palindrome("racecar"));
println!("is_palindrome('hello'): {}", is_palindrome("hello"));
let wrapped = word_wrap("The quick brown fox jumps over the lazy dog", 15);
for line in &wrapped { println!("|{:<15}|", line); }
let freq = count_words("the cat sat on the mat and the cat");
println!("\nWord frequency: {:?}", freq);
}
Resultado:
is_palindrome('racecar'): true
is_palindrome('hello'): false
|The quick brown|
|fox jumps over |
|the lazy dog |
Word frequency: {"the": 3, "cat": 2, "sat": 1, "on": 1, "mat": 1, "and": 1}
Processo TDD: Escreva os testes primeiro (
test_palindrome_simple, etc.) → Em seguida, escreva a implementação → Executecargo test.#[cfg(test)]Certifique-se de que o módulo de teste seja compilado apenas durante os testes.use super::*Importe a API pública do módulo em teste. Este exemplo demonstra uma combinação de testes unitários e execução real.
❓ Perguntas Frequentes
P: Qual é a diferença entre
#[test]e#[cfg(test)]? R:#[test]marca uma função como função de teste, enquanto#[cfg(test)]envolve um módulo para que ele seja compilado apenas no modo de teste.#[test]significa “esta função é um teste”, e#[cfg(test)]significa “este código é apenas para testes”. Eles geralmente são usados em combinação:#[cfg(test)] mod tests { ... }envolve todo o módulo de teste, e cada função de teste dentro do módulo é marcada com#[test].
P: Qual é a diferença entre doc-tests e testes de unidade? R: Os doc-tests verificam se o código de exemplo na documentação é executado corretamente, enquanto os testes de unidade verificam se a lógica da função está correta. O código dos doc-tests é escrito dentro de blocos de código anotados com
///, e ocargo testos compila e executa automaticamente. Isso garante que os exemplos na documentação estejam sempre atualizados — se o código for alterado, mas a documentação não for atualizada, os doc-tests falharão.
P: Por que os testes de integração são colocados no diretório
tests/? R: Porque cada arquivo no diretóriotests/é tratado como um crate independente e só pode acessar a API pública da sua biblioteca. Isso simula o cenário em que “usuários externos utilizam sua biblioteca”. Os testes unitários, por outro lado, são colocados no módulo#[cfg(test)]dentro dos arquivos de código-fonte e podem acessar funções privadas. Os dois têm finalidades diferentes: os testes unitários são testes de caixa branca (que examinam a implementação interna), enquanto os testes de integração são testes de caixa preta (que examinam apenas o comportamento externo).
P: Quais são os parâmetros comuns para
cargo test? R:cargo testé muito flexível:cargo test nameexecuta apenas testes com nomes correspondentes,cargo test -- --nocaptureexibe a saída do println! nos testes,cargo test -- --test-threads=1é executado em uma única thread (adequado para testes com estado compartilhado),cargo test --test filenameexecuta apenas arquivos específicos de testes de integração ecargo test -- --ignoredexecuta apenas testes marcados com#[ignore].
P: O benchmarking é necessário? Quando ele é necessário? R: O benchmarking é essencial quando seu código faz parte de um caminho crítico para o desempenho (como negociações de alta frequência, motores de jogos ou pipelines de processamento de dados). Seu valor não está em medir “a rapidez com que ele é executado”, mas em estabelecer uma linha de base de desempenho — para que, ao modificar o código, você possa detectar imediatamente qualquer degradação no desempenho. Para a maioria dos códigos empresariais, escreva primeiro testes de unidade adequados e recorra ao benchmarking somente quando o desempenho se tornar um gargalo.
📖 Resumo
#[test]marca uma função de teste;cargo testexecuta todos os testes;assert!/assert_eq!/assert_ne!são as três principais macros de asserção#[should_panic]Testar cenários de pânico previstos;#[ignore]Ignorar os testes que não precisam ser executados neste momento#[cfg(test)]Compilação condicional de módulos de teste para garantir que o código de teste não apareça nas compilações de produção///Os comentários da documentação utilizam o formato Markdown; os blocos de código são convertidos automaticamente em doc-tests;cargo doc --opengera documentação em HTML- Os testes de integração estão localizados no diretório
tests/; cada arquivo é compilado de forma independente e pode testar apenas a API pública (perspectiva de caixa preta). - Avaliação comparativa: Para medir o desempenho do código, use
#[bench]para a compilação noturna; para a versão estável, recomendamos o cratecriterion.
📝 Exercícios
-
Dificuldade ⭐: Escreva um programa que inclua a função
is_prime(n: u32) -> boolpara determinar se um número é primo. Escreva pelo menos 5 casos de teste para essa função (incluindo os casos-limite: 0, 1, 2, números primos e números compostos). Useassert!,assert_eq!e#[should_panic](que gera um erro de pânico se o parâmetro for 0) pelo menos uma vez cada. -
Dificuldade ⭐⭐: Crie uma biblioteca chamada “Conversor de Temperatura” contendo duas funções:
celsius_to_fahrenheit(c: f64) -> f64efahrenheit_to_celsius(f: f64) -> f64. Escreva comentários de documentação completos para essas duas funções (incluindo exemplos, descrições dos parâmetros e descrições dos valores de retorno) e certifique-se de que os testes unitários sejam aprovados. Além disso, escreva testes unitários para verificar valores limite (como 0 °C = 32 °F, 100 °C = 212 °F e -40 °C = -40 °F). -
Dificuldade ⭐⭐⭐: Implemente uma biblioteca “Calculadora Simples” que suporte cinco operações:
add,subtract,multiply,divideepower(operações de potência). Crie uma estrutura de testes abrangente para essa biblioteca: (1) Os testes unitários devem abranger todas as funções, incluindo casos extremos (divisão por zero, potência zero, etc.); (2) Simule a criação de um arquivo de teste de integraçãotests/calculator_integration_test.rs(escreva o conteúdo completo nos comentários) para testar cenários de “operações consecutivas” (comoadd(2,3) → multiply(5,4) → power(20,2)). Use#[should_panic]para testar o cenário de divisão por zero.