Rustのテストとドキュメント作成:信頼性の高いコードを書くための第一歩
テストはコードの「品質検査官」です。ユーザーに発見される前に、テストでバグを見つけ出しましょう。ドキュメントはコードの「取扱説明書」です。他の人に使い方を伝えるだけでなく、将来の自分に対して、なぜそのように書いたのかを思い出させてくれる役割も果たします。
コードを書くことを「家を建てる」ことに例えるなら、テストは「品質検査報告書」、ドキュメントは「取扱説明書」に相当します。テストが行われていない家に、あえて引っ越そうとするでしょうか?TDD(テスト駆動開発)の核心となる考え方は単純です。まずテストを書き、その後にコードを書く――これは、製品を製造する前に品質基準を設定するのと同じことです。
1. 学習内容
#[test]プロパティマーカーのテスト関数、cargo testテストスイートの実行assert!/assert_eq!/assert_ne!期待される動作を検証するためのアサーションマクロ#[should_panic]想定されるパニック事態のテスト#[cfg(test)]条件付きコンパイルのテストモジュール。本番ビルドからは自動的に除外される///ドキュメントへの注釈の追加とcargo docHTMLドキュメントの生成- 統合テスト(
tests/ディレクトリ内)およびベンチマークテストの概念
2. 品質検査員の物語
(1) 苦闘:品質管理のない工場
ボブは自動車工場の品質管理検査員だ。働き始めて間もなく、彼は衝撃的な事実を知った。
- この工場では1日500台の自動車を生産しているが、品質検査に合格するのは0台である
- ある顧客が、「車を買ったばかりなのに、ハンドルが四角い!」と苦情を申し立てた。
- 品質管理の責任者は、「大丈夫、顧客が自分で直してくれるから」と言った。
- さらにばかばかしいのは、その工場の「指示」が、くしゃくしゃになった紙片に「丸い方を踏んで、長い方を回す」と書かれているだけだということだ。
「もしすべての車が工場を出る前に検査されていれば、顧客が四角いハンドルを手にすることなどなかっただろう……」
(2) Rustにおけるテストとドキュメント作成のアプローチ
Rust は、よく管理された工場の品質管理システムと同様に、テストおよびドキュメント作成のための包括的なツールセットを提供しています:
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);
}
Rustのテストフレームワークは、品質管理の組立ラインのようなものです。
#[test]は各「品質管理ステーション」を表し、cargo testは組立ライン全体を起動し、assert_eq!は品質管理基準を表しています。///ドキュメントコメントは、各部品に添付された「取扱説明書」のようなものであり、cargo docは、それらすべての取扱説明書を1冊の冊子にまとめている。
3. 基本概念
(1) テストおよびドキュメント作成のフレームワーク
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) 試験形式の比較
| テストの種類 | キーワード/場所 | テスト範囲 | 類推 | 適用シナリオ |
|---|---|---|---|---|
| ユニットテスト | #[test] + #[cfg(test)] |
個々の関数またはモジュール | 各コンポーネントの確認 | 機能ロジックの検証 |
| ドキュメントのテスト | /// 内のコードブロック |
API サンプルコード | マニュアルに掲載されている例 | ドキュメント内の例が正常に動作することを確認する |
| 統合テスト | tests/ ディレクトリ内の .rs ファイル |
外部 API の全体的な動作 | 車両の路上試験 | モジュール間の連携の検証 |
| ベンチマーク | #[bench] / cargo bench |
パフォーマンス指標 | 速度テスト | パフォーマンスに敏感なコード |
(3) アサーションマクロの比較
| マクロ | 目的 | 成功条件 | 失敗メッセージ |
|---|---|---|---|
assert!(expr) |
ブール条件 | expr == true |
assertion failed: expr |
assert_eq!(a, b) |
等価性 | a == b |
assertion failed: (left == right) |
assert_ne!(a, b) |
非対称性 | a != b |
assertion failed: (left != right) |
4. テストとドキュメントの例
(1) ▶ サンプル:ユニットテストとアサーションマクロ (難易度 ⭐)
// ============================================
// 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);
}
}
出力:
=== 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
テスト出力 (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
テストモジュールは
#[cfg(test)]でラップされています。これは「テストモードでのみコンパイルする」ことを意味し、このコードは本番ビルド (cargo build --release) には一切含まれません。use super::*は、親モジュール(つまり、現在のファイル内のメインコード)からすべての関数をインポートします。#[should_panic(expected = "...")]は、特定の条件下で関数が正しくパニックを発生させることを検証します。
(2) ▶ サンプル:ドキュメントの注釈と cargo doc (難易度: ⭐⭐)
// ============================================
// 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(""));
}
}
出力:
=== 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)
///ドキュメントのコメントはMarkdown形式を使用しており、# Examples、# Parameters、# Return Valueなどのセクションに対応しています。コードブロック内のサンプルコードは自動的に「doc-tests」となります。—cargo testはこれらのコードブロックをコンパイルして実行し、ドキュメント内の例が常に正常に動作することを保証します。cargo doc --openは見栄えの良い HTML ドキュメントを生成し、ブラウザで開きます。
(3) ▶ サンプル:統合テストとテストの構成(難易度 ⭐⭐)
// ============================================
// 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);
// }
出力:
=== 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
統合テストは、プロジェクトのルートディレクトリ内の
tests/フォルダにあり、各.rsファイルは個別のクレートを表しています。統合テストでは ライブラリのパブリック API のみをテスト可能(pubでマークされたインターフェース)であり、プライベート関数にはアクセスできません。これは、「外部ユーザーがライブラリを使用する」というシナリオをシミュレートするものです。cargo test --test filenameを使用すると、特定の統合テストファイルのみを実行することができます。
(4) ▶ サンプル:ベンチマーキングの概念(難易度 ⭐⭐⭐)
// ============================================
// 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);
// });
// }
// }
出力:
=== 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
ベンチマークはコードの実行時間を測定し、リファクタリング中にパフォーマンスが低下しないことを確認するためのものです。Rust Nightly ではデフォルトで
#[bench]属性が含まれていますが、安定版リリースではcriterionクレートの使用が推奨されています。注:ベンチマークは「速ければ速いほど良い」というものではありません。重要なのは、コードを修正する際に予期せぬパフォーマンスの低下がないことを確認するためのパフォーマンスのベースラインを確立することです。 上記の例では、Instant::now()の実行時間を手動で計測することで、ベンチマークの概念を示しています。
(5) ▶ サンプル:総合演習 — テスト駆動型の文字列ライブラリ (難易度 ⭐⭐⭐)
// ============================================
// 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);
}
出力:
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}
TDDのプロセス:まずテストを書く(
test_palindrome_simpleなど)→ 次に実装を書く →cargo testを実行する。#[cfg(test)]テストモジュールはテスト実行時のみコンパイルされるようにする。use super::*テスト対象モジュールのパブリックAPIをインポートする。この例は、ユニットテストと実際の実行を組み合わせたものを示している。
❓ よくある質問
#[test] と #[cfg(test)] の違いは何ですか?#[test] は関数をテスト関数としてマークし、#[cfg(test)] はモジュールをラップして、テストモードでのみコンパイルされるようにします。tests/ ディレクトリに配置されるのですか?tests/ ディレクトリ内のすべてのファイルは独立したクレートとして扱われ、ライブラリのパブリック API にのみアクセスできるためです。📖 まとめ
#[test]はテスト関数を指定します。cargo testはすべてのテストを実行します。assert!/assert_eq!/assert_ne!は、3つの主要なアサーションマクロです。#[should_panic]想定されるパニックシナリオをテストする;#[ignore]現時点で実行する必要のないテストはスキップする#[cfg(test)]テストモジュールの条件付きコンパイルを行い、本番ビルドにテストコードが含まれないようにする///ドキュメントのコメントにはMarkdown形式が使用されており、コードブロックは自動的にdoc-testsに変換されます。cargo doc --openはHTML形式のドキュメントを生成します- 統合テストは
tests/ディレクトリ内にあります。各ファイルは個別にコンパイルされ、パブリックAPIのみをテストできます(ブラックボックス視点)。 - ベンチマーク:コードのパフォーマンスを測定するには、ナイトリービルドの場合は
#[bench]を使用してください。安定版リリースの場合は、criterionクレートを推奨します。
📝 練習問題
-
難易度 ⭐:
is_prime(n: u32) -> bool関数を使用して、ある数が素数であるかどうかを判定するプログラムを作成してください。この関数に対して、少なくとも 5 つのテストケースを作成してください(境界ケース:0、1、2、素数、合成数を含めること)。assert!、assert_eq!、および#[should_panic](パラメータが 0 の場合、パニックを引き起こす)を、それぞれ少なくとも 1 回ずつ使用してください。 -
難易度 ⭐⭐:
celsius_to_fahrenheit(c: f64) -> f64とfahrenheit_to_celsius(f: f64) -> f64の 2 つの関数を含む「温度変換」ライブラリを作成してください。これら 2 つの関数について、完全なドキュメントコメント(例、パラメータの説明、戻り値の説明を含む)を記述し、ユニットテストがパスすることを確認してください。また、境界値(0°C = 32°F、100°C = 212°F、-40°C = -40°F など)を検証するための単体テストを作成してください。 -
難易度 ⭐⭐⭐:
add、subtract、multiply、divide、power(累乗演算)の5つの演算をサポートする「Simple Calculator」ライブラリを実装してください。このライブラリのための包括的なテストフレームワークを作成してください:(1) ユニットテストでは、エッジケース(ゼロ除算、0乗など)を含め、すべての関数を網羅する必要があります。(2) 「連続演算」シナリオ(add(2,3) → multiply(5,4) → power(20,2)など)をテストするためのtests/calculator_integration_test.rs統合テストファイルの作成をシミュレートしてください(コメント欄に完全な内容を記述してください)。ゼロ除算のシナリオをテストするには#[should_panic]を使用してください。



