課題20: テストスイート構築
マンダトリー要件
問題1:テストの基礎理解(15点)
以下の質問に答えなさい。
- テストの種類(5点)
- アサーションマクロ(5点)
assert!
- assert_eq!
- assert_ne!それぞれの使用例を示すこと。
- テスト属性(5点)
#[test]
- #[should_panic]
- #[ignore]
- #[cfg(test)]問題2:電卓ライブラリのテスト(30点)
以下の電卓ライブラリを実装し、包括的なテストスイートを作成しなさい。
2.1 基本実装(10点)
// src/lib.rs
pub struct Calculator {
memory: f64,
}
impl Calculator {
pub fn new() -> Self {
Calculator { memory: 0.0 }
}
pub fn add(&mut self, a: f64, b: f64) -> f64 {
// 実装
}
pub fn subtract(&mut self, a: f64, b: f64) -> f64 {
// 実装
}
pub fn multiply(&mut self, a: f64, b: f64) -> f64 {
// 実装
}
pub fn divide(&mut self, a: f64, b: f64) -> Result<f64, String> {
// 実装(0除算はエラー)
}
pub fn store(&mut self, value: f64) {
// メモリに保存
}
pub fn recall(&self) -> f64 {
// メモリから読み込み
}
pub fn clear_memory(&mut self) {
// メモリをクリア
}
}
2.2 単体テスト(10点)
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_addition() {
// テストを実装
}
#[test]
fn test_subtraction() {
// テストを実装
}
#[test]
fn test_multiplication() {
// テストを実装
}
#[test]
fn test_division() {
// テストを実装
}
#[test]
#[should_panic(expected = "0で除算")]
fn test_division_by_zero() {
// テストを実装
}
#[test]
fn test_memory_operations() {
// store, recall, clear_memoryのテスト
}
}
要件:
- すべての公開関数に対するテスト
- 正常系と異常系の両方をカバー
- エッジケースのテスト(0、負の数、大きな数)
2.3 ドキュメントテスト(10点)
各関数にドキュメントコメントとテストを追加しなさい。
/// 2つの数を加算します。
///
/// # Examples
///
///
/// use calculator::Calculator;
///
/// let mut calc = Calculator::new();
/// assert_eq!(calc.add(2.0, 3.0), 5.0);
/// pub fn add(&mut self, a: f64, b: f64) -> f64 {
// 実装
}
要件:
- すべての公開関数にドキュメント
# Examplesセクション# Panicsセクション(該当する場合)# Errorsセクション(該当する場合)
問題3:統合テスト(20点)
実用的なシナリオをテストする統合テストを作成しなさい。
// tests/integration_tests.rs
use calculator::Calculator;
#[test]
fn test_complex_calculation() {
// (10 + 5) * 2 / 3 = 10
let mut calc = Calculator::new();
let result1 = calc.add(10.0, 5.0);
let result2 = calc.multiply(result1, 2.0);
let result3 = calc.divide(result2, 3.0).unwrap();
assert!((result3 - 10.0).abs() < 1e-10);
}
#[test]
fn test_memory_workflow() {
// メモリ機能を使った実用的なシナリオ
}
#[test]
fn test_error_recovery() {
// エラーが発生した後も正常に動作することを確認
}
要件:
- 最低5つの統合テスト
- 実際の使用シナリオを反映
- エラーハンドリングのテスト
問題4:TDDの実践(15点)
テスト駆動開発で以下の機能を追加しなさい。
4.1 平方根機能
手順:
- テストを先に書く(Red)
- 実装する(Green)
- リファクタリング(Refactor)
// 1. Red - テストを先に書く
#[test]
fn test_sqrt() {
let mut calc = Calculator::new();
assert_eq!(calc.sqrt(4.0).unwrap(), 2.0);
assert_eq!(calc.sqrt(9.0).unwrap(), 3.0);
assert!(calc.sqrt(-1.0).is_err()); // 負の数はエラー
}
// 2. Green - 実装
impl Calculator {
pub fn sqrt(&self, x: f64) -> Result<f64, String> {
// 実装
}
}
// 3. Refactor - 必要に応じて改善
提出物:
- コミット履歴で各ステップを示す
git log --onelineの出力を含める
---
ボーナス課題
ボーナス1:プロパティベーステスト(10点)
quickcheckまたはproptestを使ったプロパティベーステストを実装しなさい。
# Cargo.toml
[dev-dependencies]
proptest = "1.0"
use proptest::prelude::*;
proptest! {
#[test]
fn test_addition_commutative(a in -1000.0..1000.0, b in -1000.0..1000.0) {
let mut calc = Calculator::new();
let result1 = calc.add(a, b);
let result2 = calc.add(b, a);
// 加算は可換である
prop_assert!((result1 - result2).abs() < 1e-10);
}
#[test]
fn test_multiplication_associative(
a in -100.0..100.0,
b in -100.0..100.0,
c in -100.0..100.0
) {
// (a * b) * c == a * (b * c)
// 実装
}
}
要件:
- 最低5つのプロパティテスト
- 数学的性質の検証(可換性、結合性、分配性など)
- エッジケースの自動発見
ボーナス2:モックとスタブ(10点)
依存性を持つコードのテストを実装しなさい。
// src/calculator_service.rs
pub trait Logger {
fn log(&self, message: &str);
}
pub struct CalculatorService<L: Logger> {
calculator: Calculator,
logger: L,
}
impl<L: Logger> CalculatorService<L> {
pub fn new(logger: L) -> Self {
CalculatorService {
calculator: Calculator::new(),
logger,
}
}
pub fn add_with_log(&mut self, a: f64, b: f64) -> f64 {
self.logger.log(&format!("Adding {} + {}", a, b));
let result = self.calculator.add(a, b);
self.logger.log(&format!("Result: {}", result));
result
}
}
// テスト用モック
#[cfg(test)]
mod tests {
use super::*;
struct MockLogger {
messages: std::cell::RefCell<Vec<String>>,
}
impl MockLogger {
fn new() -> Self {
MockLogger {
messages: std::cell::RefCell::new(Vec::new()),
}
}
fn get_messages(&self) -> Vec<String> {
self.messages.borrow().clone()
}
}
impl Logger for MockLogger {
fn log(&self, message: &str) {
self.messages.borrow_mut().push(message.to_string());
}
}
#[test]
fn test_logging() {
let logger = MockLogger::new();
let mut service = CalculatorService::new(logger);
service.add_with_log(2.0, 3.0);
let messages = service.logger.get_messages();
assert_eq!(messages.len(), 2);
assert!(messages[0].contains("Adding"));
assert!(messages[1].contains("Result"));
}
}
要件:
- トレイトを使った抽象化
- テスト用のモック実装
- モックを使った振る舞いの検証
ボーナス3:カバレッジ分析(10点)
テストカバレッジを測定し、100%を目指しなさい。
# Tarpaulinのインストール
cargo install cargo-tarpaulin
# カバレッジ測定
cargo tarpaulin --out Html --out Lcov
# LLVMカバレッジ
RUSTFLAGS="-C instrument-coverage" cargo test
llvm-profdata merge -sparse default.profraw -o coverage.profdata
llvm-cov show target/debug/calculator -instr-profile=coverage.profdata
提出物:
- カバレッジレポート(HTML)
- カバレッジ率を示すスクリーンショット
- 100%に到達できない場合は理由を説明
ボーナス4:ベンチマーク(10点)
Criterionを使ったベンチマークテストを実装しなさい。
// benches/calculator_bench.rs
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use calculator::Calculator;
fn bench_operations(c: &mut Criterion) {
c.bench_function("add", |b| {
let mut calc = Calculator::new();
b.iter(|| calc.add(black_box(10.0), black_box(20.0)));
});
c.bench_function("divide", |b| {
let mut calc = Calculator::new();
b.iter(|| calc.divide(black_box(100.0), black_box(5.0)));
});
c.bench_function("complex_calculation", |b| {
b.iter(|| {
let mut calc = Calculator::new();
let r1 = calc.add(black_box(10.0), black_box(5.0));
let r2 = calc.multiply(r1, black_box(2.0));
calc.divide(r2, black_box(3.0))
});
});
}
criterion_group!(benches, bench_operations);
criterion_main!(benches);
要件:
- すべての公開関数のベンチマーク
- 複雑な操作のベンチマーク
- 結果の分析レポート
---
評価基準
マンダトリー部分(80点)
| 項目 | 配点 | 評価ポイント |
|---|---|---|
| 問題1:基礎理解 | 15点 | テストの種類と使い方の理解 |
| 問題2:電卓テスト | 30点 | 包括的なテストスイート |
| 問題3:統合テスト | 20点 | 実用的なシナリオのカバー |
| 問題4:TDD実践 | 15点 | Red-Green-Refactorの実践 |
ボーナス部分(20点)
| 項目 | 配点 | 評価ポイント |
|---|---|---|
| ボーナス1:プロパティテスト | 10点 | 数学的性質の検証 |
| ボーナス2:モック | 10点 | 依存性の分離 |
| ボーナス3:カバレッジ | 10点 | 高いカバレッジ率 |
| ボーナス4:ベンチマーク | 10点 | 性能測定 |
注: ボーナスは最大20点まで加算されます。
---
提出方法
ファイル構成
rust-foundations-20/
├── Cargo.toml
├── src/
│ ├── lib.rs # Calculator実装
│ └── calculator_service.rs # ボーナス2用
├── tests/
│ └── integration_tests.rs
├── benches/
│ └── calculator_bench.rs
├── answers.md # 問題1の回答
├── tdd_log.txt # git log出力
└── coverage/
└── index.html # カバレッジレポート
テスト実行
# すべてのテスト
cargo test
# 統合テストのみ
cargo test --test integration_tests
# ドキュメントテスト
cargo test --doc
# カバレッジ
cargo tarpaulin
# ベンチマーク
cargo bench
---
ヒント
問題2のヒント
浮動小数点数の比較:
fn assert_float_eq(a: f64, b: f64) {
assert!((a - b).abs() < 1e-10, "期待: {}, 実際: {}", b, a);
}
#[test]
fn test_division() {
let mut calc = Calculator::new();
let result = calc.divide(10.0, 3.0).unwrap();
assert_float_eq(result, 3.333333333333333);
}
問題4のヒント
TDDのコミット例:
git commit -m "Red: Add failing test for sqrt"
git commit -m "Green: Implement sqrt (naive)"
git commit -m "Refactor: Use f64::sqrt for better precision"
ボーナス1のヒント
プロパティの例:
a + b == b + a(a + b) + c == a + (b + c)a + 0 == aa - a == 0ボーナス3のヒント
カバレッジ向上のコツ:
// すべてのブランチをテスト
pub fn abs(x: f64) -> f64 {
if x < 0.0 { // この分岐
-x
} else { // とこの分岐の両方をテスト
x
}
}
#[test]
fn test_abs_positive() {
assert_eq!(abs(5.0), 5.0);
}
#[test]
fn test_abs_negative() {
assert_eq!(abs(-5.0), 5.0); // 両方のブランチをカバー
}
---
学習の確認
この課題を通じて、以下を理解できたか確認してください:
次の章では、ドキュメンテーションとrustdocを学びます。