第 1 章: TODO リストと最初のテスト¶
1.1 はじめに¶
プログラムを作成するにあたって、まず何をすればよいでしょうか?私たちは、仕様を確認して TODO リスト を作るところから始めます。
TODO リスト
何をテストすべきだろうか——着手する前に、必要になりそうなテストをリストに書き出しておこう。
— テスト駆動開発
1.2 仕様の確認¶
今回取り組む FizzBuzz 問題の仕様は以下の通りです。
1 から 100 までの数をプリントするプログラムを書け。
ただし 3 の倍数のときは数の代わりに「Fizz」と、5 の倍数のときは「Buzz」とプリントし、
3 と 5 両方の倍数の場合には「FizzBuzz」とプリントすること。
この仕様をそのままプログラムに落とし込むには少しサイズが大きいですね。最初の作業は仕様を TODO リスト に分解する作業から着手しましょう。
1.3 TODO リストの作成¶
仕様を分解して TODO リストを作成します。
TODO リスト:
- 数を文字列にして返す
- 1 を渡したら文字列 "1" を返す
- 3 の倍数のときは数の代わりに「Fizz」と返す
- 5 の倍数のときは「Buzz」と返す
- 3 と 5 両方の倍数の場合には「FizzBuzz」と返す
- 1 から 100 までの数
- プリントする
まず「1 を渡したら文字列 "1" を返す」という、最も小さなタスクから取り掛かります。
1.4 テスティングフレームワークの導入¶
テストファースト¶
最初にプログラムする対象を決めたので、早速プロダクトコードを実装……ではなく テストファースト で作業を進めましょう。
テストファースト
いつテストを書くべきだろうか——それはテスト対象のコードを書く前だ。
— テスト駆動開発
今回 Rust のテスティングフレームワークには 標準搭載のテストフレームワーク を利用します。Rust のテストは Cargo と統合されており、cargo test コマンドで簡単に実行できます。
開発環境のセットアップ¶
Cargo でプロジェクトを初期化し、テスト環境をセットアップします。
# Nix 環境に入る
$ nix develop .#rust
# プロジェクトの初期化
$ cd apps
$ cargo new rust --name fizzbuzz
$ cd rust
Rust のテストは #[cfg(test)] アトリビュートで囲んだモジュール内に書きます。テスト関数は #[test] アトリビュートを付けます。
環境確認テスト¶
環境が正しく設定されていることを確認するため、学習用テストを書きます。
// src/lib.rs
#[cfg(test)]
mod tests {
#[test]
fn test_learning_to_string_整数を文字列へ変換できる() {
assert_eq!("42", 42.to_string());
}
}
テストを実行します。
$ cargo test
running 1 test
test tests::test_learning_to_string_整数を文字列へ変換できる ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
テストが通りました。Rust の標準テストフレームワークが正常に動作することが確認できました。assert_eq! マクロは Go の if got != want パターンや Java の assertEquals に相当する、Rust のアサーション手段です。
1.5 仮実装¶
テスト環境の準備ができたので、TODO リストの最初の作業に取り掛かりましょう。
TODO リスト:
- 数を文字列にして返す
- 1 を渡したら文字列 "1" を返す
- 3 の倍数のときは数の代わりに「Fizz」と返す
- 5 の倍数のときは「Buzz」と返す
- 3 と 5 両方の倍数の場合には「FizzBuzz」と返す
- 1 から 100 までの数
- プリントする
まずはアサーションを最初に書きましょう。
アサートファースト
いつアサーションを書くべきだろうか——最初に書こう。
— テスト駆動開発
Red: 最初のテスト¶
FizzBuzz のテストを書きます。
// src/lib.rs
pub fn generate(number: i32) -> String {
todo!()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_1を渡したら文字列1を返す() {
assert_eq!("1", generate(1));
}
}
テストを実行します。
$ cargo test
running 1 test
test tests::test_1を渡したら文字列1を返す ... FAILED
thread 'tests::test_1を渡したら文字列1を返す' panicked at 'not yet implemented'
not yet implemented——まだ実装されていません。todo!() マクロは「ここはまだ未実装」であることを明示し、実行時にパニックを起こします。
Green: 仮実装¶
テストを通すために 仮実装 から始めます。
仮実装を経て本実装へ
失敗するテストを書いてから、最初に行う実装はどのようなものだろうか——ベタ書きの値を返そう。
— テスト駆動開発
generate 関数を定義して、文字列リテラルを返します。
pub fn generate(number: i32) -> String {
"1".to_string()
}
テストを実行します。
$ cargo test
running 1 test
test tests::test_1を渡したら文字列1を返す ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
テストが通りました。Rust では "1" は &str(文字列スライス)であり、"1".to_string() で String(所有権を持つ文字列)に変換します。Go の return "1" がそのまま string を返せるのとは異なり、Rust では &str と String の区別が重要です。
TODO リスト:
- 数を文字列にして返す
- 1 を渡したら文字列 "1" を返す
- 3 の倍数のときは数の代わりに「Fizz」と返す
- 5 の倍数のときは「Buzz」と返す
- 3 と 5 両方の倍数の場合には「FizzBuzz」と返す
- 1 から 100 までの数
- プリントする
ここまでの作業をバージョン管理システムにコミットしておきましょう。
$ git add .
$ git commit -m 'test: 数を文字列にして返す'
1.6 まとめ¶
この章では以下のことを学びました。
- TODO リスト で仕様をプログラミング対象に分解する方法
- テストファースト で最初にテストを書く考え方
- Rust の標準テストフレームワーク(
#[test]、assert_eq!)のセットアップ - 仮実装 でベタ書きの値を返してテストを通す手法
- アサートファースト でテストの終わりから書き始めるアプローチ
- Rust の
&strとStringの違い
次章では、2 つ目のテストケースを追加して 三角測量 を行い、プログラムを一般化していきます。