はじめに
Rat A Tat!!! nikkieです。
先日ちょうぜつ本でFizzBuzzをテストファーストで実装しました(BDD)。
同じ題材をt-wadaさん(国内でTDDの第一人者と勝手に思ってます)がライブコーディングされていたのを思い出し、視聴 & ペアプロのように写経してみました。
目次
- はじめに
- 目次
- TDD Boot Camp 2020 Online #1
- お題をTODOリストに変換
- 1周目:最小限の実装(仮実装)
- 2周目:三角測量
- 3周目:仮実装だが、三角測量を経ない
- 4周目:明白な実装
- ライブコーディングのまとめ
- 終わりに
TDD Boot Camp 2020 Online #1
オンライン開催されたTDD Boot Camp!
TDD の伝道師、和田卓人さんをお招きし、講演、指導を頂きます。
アーカイブが残っているのはとてもありがたいですね。
https://www.youtube.com/watch?v=Q-FJ3XmFlT8&t=1145s
今回はライブコーディングパートを見ていきます1。
ライブコーディングのお題はFizzBuzz。
実務コードより簡単なお題ですが、小さなお題にしたことで何回もテスト駆動開発のサイクル(Red-Green-Refactor)を回す様子を目にできます!2
t-wadaさんはJavaですが、私はPythonに読み替えて写経していきました。
コードはこちらです:
お題をTODOリストに変換
以下のようなリストに変換されます
- [ ] 数をそのまま文字列に変換する - [ ] 3の倍数のときは数の代わりに「Fizz」に変換する - [ ] 5の倍数のときは数の代わりに「Buzz」に変換する - [ ] 15の倍数のときは数の代わりに「FizzBuzz」に変換する
これらはテスト容易性が高く、重要度も高い項目です。
「数をそのまま文字列に変換」が正常系で、ほか3つが準正常系
FizzBuzz問題としては「1から100までの数」「プリントする」という要素もあります。
これらはテスト容易性が低く、重要度も低いと、後回しにされています
- 「1から100までの数」は、代わりに「1からNまでの数」にしたい。Nを後で決める
- 「プリントする」はテストコードで書くのは(言語によるけれど)大変で、目でチェックもできる。後回しにしよう
- ビューのテストに通じる考え方
1周目:最小限の実装(仮実装)
正常系「数をそのまま文字列に変換する」からテストを書いていきます。
まだ何もない1周目は重いので、小さい項目を選ぶ!
1周目で行われたのは以下です:
- 設計
- パッケージ・ファイル・クラス・メソッドを決める
- テストコードのテスト
- 最小限の実装により達成
まずはスタートラインに立てていることの確認。
IDEの力で絶対に失敗するテスト(fail
呼び出し)を生成し、予想通り失敗することを確認します。
class TestFizzBuzz: def test(self): assert False
スタートラインに立てていたら、テストメソッドを実装。
テストメソッドは、検証 -> 実行 -> 準備の順で書きます。
理由は検証がテストのゴールだから、まず書いてしまう
検証を書こうとすると、「数をそのまま文字列に変換する」は抽象度が高く、期待値で手が止まります。
そこで具体的な「1を渡すと文字列1を返す」を追加して、この実装を進めます。
この中でテスト対象のクラスやメソッド名が決まります。
ポイントは「作る前に使う」こと。
テストメソッドの中で、実装がなくても使います。
使うを先行させることで、作った後に使いづらいとなってしまう悲劇を回避する機会を得られるのですね。
コンパイルエラーもサイクルのRed。
IDEの機能を使ってクラスやメソッドを実装(nullを返す)し、「1を渡すと文字列1を返す」テストが落ちます🟥
開発者はモードを切り替えて最小限の実装でテストを通します。
return "1"
と固定値を返してテストが通りました🟩
TDDの言葉では「仮実装」とも呼ばれます。
class FizzBuzz: def convert(self, n: int) -> str: - raise NotImplementedError + return "1"
なぜ仮実装でGreenにするのか。
腑に落ちたのは「テストコードのテストをしている」ということ。
仮実装をしたのにテストがRedのままの場合、仮実装はほぼ間違えようがないので、テストコードを誤ったと切り分けできる!
使うタイミングを早期に持ってくるのと同様に、テストコードのテストを早期に持ってくる。そのための仮実装、という気付きがありました
2周目:三角測量
2周目は、仮実装を目指すべき方向へ持っていくため、三角測量という手法が紹介されます。
「2を渡すと文字列2を返す」テストを追加。
これを返す実装として、引数(整数)を文字列に変換して実装します。
だいぶ一般化されました〜
class FizzBuzz: def convert(self, n: int) -> str: - return "1" + return str(n)
テストの追加にあたり、検討されたのが以下の2つ
- 1つのテストメソッドにassertを追加
- テストメソッドを新規追加
選択されたのは後者です。
ユニットテスト(単体テスト)は1つのテストメソッドに1つのアサーションとしておくことで、今実行したら全X件のうちY件通るはずと予想できますね(1つの大きなテストメソッドだとアサーションが成り立たないところで止まってしまう)。
前者はアンチパターンで「Assertion Roulette」と呼ばれるそうです
3周目:仮実装だが、三角測量を経ない
「3を渡すと文字列Fizzを返す」テストを追加します。
追加した際に、テストコード中の重複に気付きます。
今回は「最短でGreenにして、テストコードの重複をRefactor」とペアで決めたとし、仮実装をします🟩
class FizzBuzz: def convert(self, num: int) -> str: if num == 3: return "Fizz" return str(num)
テストメソッドの前準備の重複解消はテスティングフレームワークがサポートしている3ので、それを使ってRefactor。
その後実装を見ます。
ここで、6の例を追加するのでしょうか?
仮実装〜三角測量とした1周目・2周目のときと今の状況は違います。
テストコードはテスト済みで、これまでの周回よりも自信を持って進めています。
そこで、三角測量(6の例追加)はせずに、仮実装をより一般的な実装に変えました。
4周目:明白な実装
続いては「5を渡すと文字列Buzzを返す」ケース。
テストケースを追加し、「3件は通り、1件は落ちる」と予想できます。
テストを実行すると、予想通り!
状況はコントロールできているのです!!4
TDDで進めた開発の状況に自信を持っているので、仮実装をせずに、一般的な実装をします。
これを「明白な実装」と呼ぶそうです。
ライブコーディングのまとめ
歩幅の調整も学びでした。
仮実装+三角測量、仮実装で三角測量なし、明白な実装と調整して進むんだ!
小さいフィードバックを頻度高く得ているから調整が可能になっているんだと思います。
なお、このあとは「実はここまでに書いたコードは未来に仕様を伝えられていなかったのです(テストコードが動作するドキュメントになっていない)」という大どんでん返しがあったのですが、今回はいったん積むことにしました。
終わりに
t-wadaさんによる、TDD Boot Camp 2020 Online #1 基調講演/ライブコーディングをアーカイブで見て、写経(ペアプロ)しました。
TDDは普段からやっていますが、t-wadaさんの所作から気付きもありましたし、達人が考えていることが全部言語化されていたので、「仮実装をなんでやるのか」「必ず最小限の実装をするのか」といったところへの回答も得られました。
ケント・ベックの『テスト駆動開発』(大部分積ん読)も読みたくなってきた〜!
このアーカイブはコメント欄も盛り上がっていて、追いきれていないのでどこかでまた見返したいなと思います(積んだパートもありますからね)。
TDD Boot Campをオンラインで開催いただき、ありがとうございました!
- 引用しているスライドですが、t-wadaさんの過去のスライドの中から近いものを選択しました(アーカイブ中のスライドとは完全には一致しないでしょう)↩
- リズムを学ぶ機会でもありますね。ref: 読書ログ | gihyo.jp の「和田卓人の“テスト駆動開発”講座」から講義編の記事を読みました🦁 - nikkie-ftnextの日記↩
-
pytestでは
setup_method
でした。ref:https://docs.pytest.org/en/latest/how-to/xunit_setup.html#method-and-function-level-setup-teardown↩ - 『Clean Craftsmanship』のビデオでUncle Bobがめっちゃ上機嫌だったのは、こういうことか!↩