第 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 テスティングフレームワークの導入¶
テストファースト¶
最初にプログラムする対象を決めたので、早速プロダクトコードを実装……ではなく テストファースト で作業を進めましょう。
テストファースト
いつテストを書くべきだろうか——それはテスト対象のコードを書く前だ。
— テスト駆動開発
今回 Python のテスティングフレームワークには pytest を利用します。
開発環境のセットアップ¶
uv プロジェクトに pytest を追加して、テスト環境をセットアップします。
# pyproject.toml
[project]
name = "fizzbuzz"
version = "0.1.0"
description = "FizzBuzz TDD project for Python"
requires-python = ">=3.11"
[tool.pytest.ini_options]
testpaths = ["test"]
pythonpath = ["."]
[dependency-groups]
dev = [
"pytest>=8.0",
]
環境確認テスト¶
環境が正しく設定されていることを確認するため、簡単なテストを書きます。
# test/test_hello.py
def test_greeting():
assert greeting() == "hello world"
def greeting():
return "hello world"
テストを実行します。
$ uv run pytest -v
test/test_hello.py::test_greeting PASSED
1 passed
テストが成功すれば、開発環境のセットアップは完了です。
1.5 最初のテストを書く¶
アサートファースト¶
TODO リストの最初の項目「1 を渡したら文字列 "1" を返す」に取り掛かります。
アサートファースト
いつアサーションを書くべきだろうか——最初に書こう。
— テスト駆動開発
テストコードを日本語で記述します。pytest では関数名にそのまま日本語を使えるため、ドキュメントとしての可読性が上がります。
# test/test_fizzbuzz.py
from lib.fizzbuzz import FizzBuzz
class TestFizzBuzz:
def setup_method(self):
self.fizzbuzz = FizzBuzz()
def test_1を渡したら文字列1を返す(self):
assert self.fizzbuzz.generate(1) == "1"
Red: テストを実行して失敗させる¶
テストを実行します。
$ uv run pytest -v
test/test_fizzbuzz.py::TestFizzBuzz::test_1を渡したら文字列1を返す FAILED
E ModuleNotFoundError: No module named 'lib.fizzbuzz'
FizzBuzz クラスが定義されていないというエラーが出ました。まだ作っていないのですから当然です。
Green: 仮実装でテストを通す¶
最初のテストを通すために 仮実装 から始めましょう。
仮実装を経て本実装へ
失敗するテストを書いてから、最初に行う実装はどのようなものだろうか——ベタ書きの値を返そう。
— テスト駆動開発
FizzBuzz クラスを定義して、文字列リテラルを返す generate メソッドを作成します。
# lib/fizzbuzz.py
class FizzBuzz:
def generate(self, number: int) -> str:
return "1"
テストを実行します。
$ uv run pytest -v
test/test_fizzbuzz.py::TestFizzBuzz::test_1を渡したら文字列1を返す PASSED
1 passed
テストが通りました!「え?こんなベタ書きのプログラムでいいの?」と思われるかもしれませんが、この細かいステップに今しばらくお付き合いください。
TODO リスト:
- 数を文字列にして返す
- 1 を渡したら文字列 "1" を返す
- 3 の倍数のときは数の代わりに「Fizz」と返す
- 5 の倍数のときは「Buzz」と返す
- 3 と 5 両方の倍数の場合には「FizzBuzz」と返す
- 1 から 100 までの数
- プリントする
1.6 まとめ¶
この章では、TDD の最も基本的なステップを体験しました。
- TODO リストの作成 — 仕様を小さなタスクに分解する
- テストファースト — テスト対象のコードを書く前にテストを書く
- アサートファースト — テストの終わりにパスすべきアサーションを最初に書く
- 仮実装 — 失敗するテストを通すために、ベタ書きの値を返す
次の章では、三角測量によってプログラムを一般化していきます。