動画:TDD Boot Camp 2020 Online #1 基調講演/ライブコーディング
テスト駆動開発との出会い
この記事(@t_wadaに学ぶテスト駆動開発【CARTA 23新卒研修】)を読んで、テスト駆動開発という言葉を知り、調べてみた。すると、なんと上記記事の研修を担当したt_wadaさんがライブコーディングをしながらテスト駆動開発について語っているYouTube動画を見つけた。動画の長さは2時間越えだが、これはみるしかないと思い観てみた。
まず、t_wadaさんの知識量と幅広さに驚いた。例えば、下記の4冊は、一時期出版社(ピアソンエデュケーション)の事情により絶版になったのが、重要な本として修正加筆されて新たに出版されたそうだ。また、ウォード・カニンガム(Ward Cunningham)、ケント・ベック(Kent Beck)、ロン・ジェフリーズ(Ron Jeffries)の三人は、エクストリームプログラミングをはじめ、ペアプログラミング、テスト駆動開発などの現代のモダンなプログラミングを支えるプラクティスを生み出した仲間だそうだ。これは読むしかなさそうだ。
リファクタリング(第2版) 既存のコードを安全に改善する [ Martin Fowler 著 ]
達人プログラマー(第2版) 熟達に向けたあなたの旅 [ Andrew Hunt ]
「大きなインパクトを残した本は、印象的な書き出しから始まる」らしい。
「動作するきれいなコード」。Ron Jeffriesのこの簡潔な言葉が、テスト駆動開発(TDD)のゴールだ。動作するきれいなコードはあらゆる意味で価値がある。
ーKent Beck 著, 和田 卓人 訳 『テスト駆動開発』まえがき
テスト駆動開発(TDD)は、頭に浮かんだコードをエディタを通して一筆書きしたら「動作するきれいなコード」を書ける天才プログラマーではない「普通の人」が、「動作するきれいなコード」を書くためのプラクティス。「動作するきれいなコード」を一気に書くのは難しいから、「動作するきれいなコード」を「動作するコード」と「きれいなコード」の二つに分けて倒していく。具体的には以下のサイクルがあるそうだ。
テスト駆動開発とは
(t_wadaさんのスライドのスクリーンショットです。)
- 「次の目標を考える」では、To Doリストのような形(箇条書き)で、紙やテキストファイルにダンプする(書き出す)。この後、Red,Green,Refactoringのサイクルを何回も回すと集中して視野が狭くなるから、最初に退避しておく。そして、複数書き出したら、重要度またはテスト容易性が高いタスクから取り組む。テスト容易性が高いタスクから始めるのがおすすめ。習熟すると、重要度とテスト容易性の2軸からなる4象限において、「重要度とテスト容易性ともに高い象限」と「重要度とテスト容易性ともに低い象限」の2つに寄せていくことができるそうだ。
- 「その目標を示すテストを書く」では、利用者(使い手)の視点からテストを書く。実装よりテストを先に書くことを「テストファースト」という。
- 「そのテストを実行して失敗させる」では、まだ実装していないから絶対失敗する。テスティングフレームワーク(テスト自動化の仕組み)は伝統的に赤色(Red)でプログラマーに失敗を伝えてくれる。
- 「目的のコードを書く」では、実装者のモードにチェンジして、失敗しているテストを成功させるための必要最小限の最短時間で成功するコードを書く。コードの綺麗さとかは問わない。コピーアンドペーストなど汚い手や汚い書き方をしてもいい。
- 「2で書いたテストを成功させる」と、テスティングフレームワークは緑色(Green)でテストの成功をプログラマーに伝えてくれる。
- 「テストが通るままでリファクタリングを行う」。リファクタリングとは、「ソフトウェアの外部から見た振る舞いを変えないままで、理解や保守が簡単になるようにソフトウェアの内部をきれいにしていくこと」(by Fowler)。Kent Beckさんは、これを「成功しているテストが成功しているままで(プロダクト/テストどちらの)コードをきれいにする」と言い換えて、テスト駆動開発に組み込んだ。
リファクタリングのやめ時は、5~10min経った時または、重複を1箇所にし終わった時。そうなったら、1に戻る。 - 「1〜6を繰り返す」。1に戻ってテスト駆動開発における設計書、つまりTo Doリストに改訂を入れる(テスト駆動開発では常に設計し続ける)。そして、1件失敗1件成功を、2件成功になるように最短で書いて、またリファクタリングを行う。
デモ
1「次の目標を考える」
(スキルだから練習しないとできるようにならない。練習すればできるようになる。)
—–書き出す——————————————————————————————————————
テスト容易性:高 重要度:高
– [ ] 数を文字列に変換する。
ただし、(の後は、準正常系とか例外系とか通常ではない振る舞いが入っている。)
– [ ] 3の倍数のときは数の代わりに「Fizz」に変換するとプリントする。
– [ ] 5の倍数のときは数の代わりに「Buzz」に変換するとプリントする。
– [ ] 3と5両方の倍数のときは数の代わりに「FizzBuzz」に変換するとプリントする。
-[ ] 1からNまで
-[ ] 1から100まで
テスト容易性:低 重要度:低
– [ ]プリントする
———————————————————————————————————————————-
Tips:
– 表現をそろえると、大事なところが浮き彫りになる。
– チェックボックスをつくる。
– テスト容易性が低いもの後回しにする。
– テスト容易性は、「観測が簡単であること」「制御が簡単であること」「対象が十分小さいこと」という3つの要素に支えられている。Clean Architectureでは、「入出力からの遠さ」で測っている。
– タスク分解はスキルだから、「経験することで学ぶ」のがいいが、「設計の本で理論を学ぶ」という方法もあり、両方するのがBetter。
—To Do(第一稿)————————————————————————————————————
テスト容易性:高 重要度:高
– [ ] 数を文字列に変換する。
- [ ] 1を渡すと文字列”1″を返す
– [ ] 3の倍数のときは数の代わりに「Fizz」に変換する
– [ ] 5の倍数のときは数の代わりに「Buzz」に変換する
– [ ] 3と5両方の倍数のときは数の代わりに「FizzBuzz」に変換する
テスト容易性:低 重要度:低
– [ ] 1からNまで
– [ ] 1から100まで
– [ ]プリントする
2「その目標を示すテストを書く」
設計の第一歩は「名前をつけること」。
母語でテストを書く(ただし、日本語が使えるチームに限る)。
テストの四要素は「準備(arrange)」「実行(act)」「検証(assert)」「後片付け」。前者三つは、スリーAパターンと呼ばれる。振る舞い駆動開発(Behavior Driven Development)では、前者三つを、”Given”,”When”,”Then”と言い換えられた。
テスト駆動開発では、「検証」(最後=GOAL)から行う。自動テストの基本形は「値の比較(値が期待値と同じか比較する)」。
手が動かなくなったら、To Doに戻って抽象度を下げる。例えば、「数を文字列に変換する」→「1を渡すと文字列”1″を返す」。あとになると修正が大幅に増加し取り返しがつかないから、早く手が止まるほうがいい。テストは常に具体的になるので、なんとなくでは進めることはできない。
3「そのテストを実行して失敗させる」
aseert関数の実測値(actual)と期待値(expected)の順番は、テスティングフレームワークごとに前後逆であったりするから、最初に確認する。1000件くらいテスト作ってからの修正は大変。
4「目的のコードを書く」
最短で。
5「2で書いたテストを成功させる」
テストコードは、ディフェクトインサーション(わざとプロダクトコードに判別可能な誤りを入れてテスト)する。ディフェクトインサーションを自動的に行うテクニックが、「ミューテーションテスティング(変異性テスト)」。プロダクトコードの中の機械的に変更可能な箇所を1箇所ずつ変えながら、テストを全件実行する。「プロダクトコードのいじれるポイント×テストの全件実行分」ミューテーションテスティングすると、(理論的にできるけど)実行コストが非常に高い。だから、最初にテストのテストを行う。これを「仮実装」という。
6「テストが通るままでリファクタリングを行う」
プロダクトコード→テストコードの順。冗長性の削減とコメントの修正など。
7「1〜6を繰り返す」
- [ ] 数を文字列に変換する。
- [x] 1を渡すと文字列"1"を返す -> 仮実装
1週目は仮実装にすることで、「設計に集中」し、「テストのテスト」に集中できる。ただし、ひどい実装が残る。
- [ ] 数を文字列に変換する。
- [x] 1を渡すと文字列"1"を返す -> 仮実装
- [ ] 2を渡すと文字列"2"を返す -> 三角測量(別の数を与えることによってまともな実装に戻す方法)
アサーションルーレットというassert()を縦に何行も増やす方法は、「失敗時にデバッグしないといけない」と「ドキュメントとして読んだときに仕様が伝わりにくい」という2つの弱点がある。よって、一つのメソッド(テスト関数)に一つのassert()を書く。(特にunit testやsmall testにおいては、この1assertion per testが良い。Integration TestやE2E(End to End)Testなどの動作が重いテストは、assertion1つという原則は破って良い。)
- [x] 数を文字列に変換する。
- [x] 1を渡すと文字列"1"を返す -> 仮実装
- [x] 2を渡すと文字列"2"を返す -> 三角測量(別の数を与えることによってまともな実装に戻す方法)
- [x] 3の倍数のときは数の代わりに文字列"Fizz"を返す。
- [x] 3を渡すと文字列"Fizz"を返す。 -> 仮実装 -> 実装- [x] 5の倍数のときは数の代わりに文字列"Buzz"を返す。
- [x] 5を渡すと文字列"Buzz"を返す。 -> 明白な実装(Obvious Implementation)
三角測量したコードは、メンテナンスコストになるから削除する。
テストコードは、「動作するドキュメントとしてのツリーになっていて」、「メンテナンスコストとして必要最小限のテストを残す」べし。
「テストの構造化とリファクタリング」を行わないと、テストのメンテナンスコストが高くなる(メンテナビリティが低い=可読性、理解容易性、変更容易性、テスト容易性が低い)。
感想
t_wadaさんの言語化レベルが非常に高く、説明の順序や言葉の選び方、全てがわかりやすさにつながっていて、とてもわかりやすく尊敬の念が溢れました。また、英語もできるのだろうなと感じました。『テスト駆動開発』を翻訳されていることを考えると、間違いないですね。話のネタが英語圏の話題であったり、英語発の用語説明もわかりやすかったです。学びになりました。ありがとうございました。
参考:
ナガリョーさんの記事です。c言語でテスト駆動開発をやるにはassert関数が使えそうです。
assertでテスト駆動C言語
t_wadaさんが動画内で以下の本もおすすめされていました。
実践テスト駆動開発 テストに導かれてオブジェクト指向ソフトウェアを育て (Object oriented selection) [ スティーブ・フリーマン ]