テスト駆動開発の世界をのぞいてみよう¶
(IMO)TDDは、環境構築の確認でやったことをもっと徹底的にやる
環境構築できていれば
assert False
で落ちるassert True
に変えればテストは全部通る
それにより、終始 コントロールしている感覚
テスト駆動開発¶
Test Driven Development(テストが開発を駆動する)
私の言葉で説明すると
テストシナリオのリストを書く
テストシナリオから 1つだけ 選び、テストコードを書き、テストが失敗することを確認
プロダクトコードを変更し、すべてのテストコードを成功させる
テストコードやプロダクトコードをリファクタリング(必要だと思ったらやる)
テストリストが空になるまで2に戻って繰り返す
サイクル ♻️「テストリスト (2) -> Red (2) -> Green (3) -> Refactor (4)」
Red(先にテストコードを書いて落とす):テストファースト
pytestでテストの書き方は皆さん体験したので、ここでは テストを書く順番・開発を進める流れにご注目 ください
「動作するきれいなコード」
まず動作させる(Red -> Green)
次に動作したままきれいなコードにする(Green -> Refactor)
FizzBuzzを例にテスト駆動開発¶
コミットログ版:https://github.com/ftnext/first-python-test-2024/commits/main/tdd
FizzBuzzの仕様からテストリストを書き出す
- [ ] 数をそのまま文字列に変換する
- [ ] 3の倍数のときは数の代わりに「Fizz」に変換する
- [ ] 5の倍数のときは数の代わりに「Buzz」に変換する
- [ ] 15の倍数のときは数の代わりに「FizzBuzz」に変換する
- [ ] 数をそのまま文字列に変換する
- [ ] 1を渡すと文字列1を返す
- [ ] 3の倍数のときは数の代わりに「Fizz」に変換する
- [ ] 5の倍数のときは数の代わりに「Buzz」に変換する
- [ ] 15の倍数のときは数の代わりに「FizzBuzz」に変換する
1を渡すと文字列1を返す
def test_1を渡すと文字列1を返す():
assert fizzbuzz(1) == "1"
実装はまだしていない(呼べるように関数定義だけしておく)
def fizzbuzz(n: int) -> str:
raise NotImplementedError
🟥テストが落ちる (0/1)
仮実装¶
test_1を渡すと文字列1を返す
だけを通すことを考える。
def fizzbuzz(n: int) -> str:
- raise NotImplementedError
+ return "1"
🟩テストが通る (1/1)
「茶番では?」
テストを書き間違える可能性がある
文字列 "1" を返すように実装して、テストがRedのままであれば、テストを間違えていることに気づける
文字列 "1" を返すように実装して、テストがGreenならば「想定通り。実装もテストコードもコントロールして進めている」
テストコード・実装どちらかにRefactorの余地はあるか? -> なさそう
『テスト駆動開発』第2章
コードでまずベタ書きの値を使い、
三角測量¶
FizzBuzzとしては常に文字列の1を返すのはよくない。 一般化したい
一般化するために、テストケースを追加する
- [ ] 数をそのまま文字列に変換する
- [x] 1を渡すと文字列1を返す
- [ ] 2を渡すと文字列2を返す
- [ ] 3の倍数のときは数の代わりに「Fizz」に変換する
- [ ] 5の倍数のときは数の代わりに「Buzz」に変換する
- [ ] 15の倍数のときは数の代わりに「FizzBuzz」に変換する
2を渡すと文字列2を返す
def test_2を渡すと文字列2を返す():
assert fizzbuzz(2) == "2"
🟥テストが落ちる (1/2)
実装する。ここで一般化
(n
が 1
のときはと分岐するより、一般化したほうが実装を単純なまま=きれいに保てる)
def fizzbuzz(n: int) -> str:
- return "1"
+ return str(n)
🟩テストが通る (2/2)
『テスト駆動開発』第3章より
コードを一般化できるのは、2つ以上の実例があるときだけ
Refactor:実装の変数をrename。 n
より number
の方が読みやすい
(パラメタ化テストは、私はいったん置いておく)
明白な実装¶
- [x] 数をそのまま文字列に変換する
- [x] 1を渡すと文字列1を返す
- [x] 2を渡すと文字列2を返す
- [ ] 3の倍数のときは数の代わりに「Fizz」に変換する
- [ ] 3を渡すと文字列Fizzを返す
- [ ] 5の倍数のときは数の代わりに「Buzz」に変換する
- [ ] 15の倍数のときは数の代わりに「FizzBuzz」に変換する
def test_3を渡すと文字列Fizzを返す():
assert fizzbuzz(3) == "Fizz"
🟥テストが落ちる (2/3)
def fizzbuzz(number: int) -> str:
if number % 3 == 0:
return "Fizz"
return str(number)
🟩テストが通る (3/3)
Refactorの余地はなさそう
『テスト駆動開発』第2章
すぐに頭の中の実装をコードに落とす。
テストリスト -> Red -> Green -> Refactor を回す¶
5の倍数
- [x] 数をそのまま文字列に変換する
- [x] 1を渡すと文字列1を返す
- [x] 2を渡すと文字列2を返す
- [x] 3の倍数のときは数の代わりに「Fizz」に変換する
- [x] 3を渡すと文字列Fizzを返す
- [ ] 5の倍数のときは数の代わりに「Buzz」に変換する
- [ ] 5を渡すと文字列Buzzを返す
- [ ] 15の倍数のときは数の代わりに「FizzBuzz」に変換する
def test_5を渡すと文字列Buzzを返す():
assert fizzbuzz(5) == "Buzz"
🟥テストが落ちる (3/4)
def fizzbuzz(number: int) -> str:
if number % 3 == 0:
return "Fizz"
+ if number % 5 == 0:
+ return "Buzz"
return str(number)
🟩テストが通る (4/4)
Refactorの余地はなさそう
15の倍数
- [x] 数をそのまま文字列に変換する
- [x] 1を渡すと文字列1を返す
- [x] 2を渡すと文字列2を返す
- [x] 3の倍数のときは数の代わりに「Fizz」に変換する
- [x] 3を渡すと文字列Fizzを返す
- [x] 5の倍数のときは数の代わりに「Buzz」に変換する
- [x] 5を渡すと文字列Buzzを返す
- [ ] 15の倍数のときは数の代わりに「FizzBuzz」に変換する
- [ ] 15を渡すと文字列FizzBuzzを返す
def test_15を渡すと文字列FizzBuzzを返す():
assert fizzbuzz(15) == "FizzBuzz"
🟥テストが落ちる (4/5)
def fizzbuzz(number: int) -> str:
+ if number % 15 == 0:
+ return "FizzBuzz"
if number % 3 == 0:
return "Fizz"
if number % 5 == 0:
return "Buzz"
return str(number)
🟩テストが通る (5/5)
Refactor:最初に見たStructural Pattern Matchingに書き換えてみてもよいかも
テストリストが全部実装できた。FizzBuzz完成!
注釈
動作する仕様書にできる
TDDBC(後述)
クラスを使って構造化する例
三角測量に使った例は消してしまう
pytestの機能も使える¶
モック
モックを使うことで、使う部品ができていなくてもテストを書ける(全通し以外の選択肢を持てることが大きい)
例:https://github.com/Uberi/speech_recognition/blob/3.11.0/tests/recognizers/test_google.py
パラメタ化
Refactorで適用することが多い
次に書きたいテストを書きやすくするためにパラメタ化する
フィクスチャ
Refactorで、Arrange(やAssert)をスッキリさせるために適用
最初に書いたテストコードはフィクスチャたくさんになりがち
これをフィクスチャごとに切り出す
読めないかもしれないですが:https://github.com/ftnext/sphinx-new-tab-link/blob/v0.6.1/tests/test_roles.py
この先の学習リソース¶
pytestではないが、pytestに置き換えて進めていけるはず
『ちょうぜつソフトウェア設計入門』(ちょうぜつ本)第6章
まとめ:体験したもの¶
自動テスト
pytest
パラメタ化・フィクスチャ・モック
開発者テスト
参加する前と比べて少しでもテストコードを書けるようになった!(と感じていただけていたら嬉しいです)
振る舞いを変えていないかという不安は、テストコードで退屈に変わります
テストファースト
テスト駆動開発(テストリスト -> Red -> Green -> Refactor)をのぞいてみた
Next: 達人のテスト駆動開発は実は単位が小さい¶
1サイクル:テストリスト -> Red -> Green -> Refactor
達人は、テストリスト -> Red -> Green -> Red -> Green -> ... -> Red -> Green -> Refactor