2019.06.15 TDDBC札幌2019「見てわかるテスト駆動開発」
基調講演+ライブコーディング
印象に残った部分のメモ
「動作するきれいなコード」をはじめから書くのは難しい
天才ははじめからできるが凡才はどうするか
- 「分割統治」
- まず動作するコードを書く
- その後できれいなコードを書く
TDDのサイクル
目標を考える
よくあるTDDの誤解=設計をしない
でも設計はやりすぎない
実装をしていくと集中はできるけど視野は狭くなる
はじめにTODOリストを作る
コードを書いて初めて分かることもある
その時は適宜TODOリストを更新する目標を示すテストを書く
例:10個のTODOリスト
10項目のTODOリストを1つずつ倒していく
まず弱そうなやつから倒していく
TDDの1周目は重い
使う側の視点からコードを書くそのテストを実行して失敗させる
目的のコードを書く
動かすことに集中する
コピペでも構わない
この時点では動作する汚いコード2で書いたテストを成功させる
テストが通るままでリファクタリングを行う
リファクタリングはTDDとは独立したテクニック
動作しているテストが動作しているままで、テストが赤くなった→外部から見た振る舞いが変わったということ1~6を繰り返す
大半のコードは動作する汚いコード
きれいにしようとして動かなくなることを恐れる
動いていないことに気づかないことが一番怖い
ソフトウェアで大事なのが動くものを提供すること
TDDのライブコーディングのサンプル FizzBuzz
まずTODOリストでやることを明確にする
構成要素を分解していく
大きいタスクを小さなタスクに分割していく
1から100まで数をプリントする部分は後回しにする
標準出力のテストは難しい
すぐ壊れやすいViewのテストは費用対効果が低い
Viewにロジックがなければテストする必要がない
TDDはテスト容易性の設計
Viewまわりはテストする必要がないようにロジックを書かない
テスト容易性を高めていく
// TODOリスト [ ] 数を文字列に変換する ただし、 [ ] 3の倍数のときはFizz [ ] 5の倍数のときはBuzz [ ] 3と5の両方の倍数のときはFizzBuzz
これらの部分がロジック
TODOリストの作成を通して、表記の揺れをなおす、仕様を明確にする
TDDの一周目が重いのは無から始めるから
ファイル名は何にするか、クラス名はなにするかなど...
テストファイルを作りすぐ実行する
テストコードを書いてからテストを実行するとハマることがある
赤くなる理由は、Unitテストのインストールが上手くいっていない場合などもある
スタートラインに立っていることを確認する
テストメソッド名は日本語でよい
テストコード=ドキュメント
何をテストしているのか明確にわかるようにする
擬似コード public class test { public function test(){ // 準備 // 実行 // 検証 } }
検証がゴールなので下から順に書いていく
何をしているのかが明確になる
単一のゴールから書く
DBからデータを取ってあれこれしてと準備のコードが多くなると、まとめてassertをたくさん書いて検証も多くしたくなる
ゴールは期待値 expected
手が動かないときは、TODOリストの抽象度を下げる
具体化する
「数を文字列に変換する」→「1を渡すと文字列"1"に変換する」
// TODOリスト [ ] 数を文字列に変換する [ ] 1を渡すと文字列"1"に変換する ただし、 [ ] 3の倍数のときはFizz [ ] 5の倍数のときはBuzz [ ] 3と5の両方の倍数のときはFizzBuzz
// 準備 // 実行 クラス名を考える String actual = fizzBuzz.stringfy(1); // 検証 assertEquals("1", actual)
作る前に使ってみる
使いやすいかは使わないとわからない
使いやすさにフォーカスする
// 準備 FizzBuzz fizzbuzz = new FizzBuzz(); // 実行 クラス名を考える String actual = fizzBuzz.stringfy(1); // 検証 assertEquals("1", actual)
public class FizzBuzz { public String stringfy(int i){ return "1" } }
コンパイルエラーから進歩した
TDDの1週目は考えることが多くて重いので小さめのタスクから書く
グリーンにする
// 準備 FizzBuzz fizzbuzz = new FizzBuzz(); // 実行&検証 assertEquals("1", fizzBuzz.stringfy(1))
リファクタリングに着手する テストコードもリファクタリングする
// TODOリスト [ ] 数を文字列に変換する [x] 1を渡すと文字列"1"に変換する [ ] 2を渡すと文字列"2"に変換する
assertEquals("2", fizzBuzz.stringfy(2)) assertEquals("1", fizzBuzz.stringfy(1)) assertEquals("1", fizzBuzz.stringfy(1)) assertEquals("1", fizzBuzz.stringfy(1)) assertEquals("1", fizzBuzz.stringfy(1)) assertEquals("1", fizzBuzz.stringfy(1)) assertEquals("1", fizzBuzz.stringfy(1))
複数のアサーションを書くと、失敗したアサーションで終了してしまい、後のアサーションは実行されない
1つのテスト関数に複数のアサーションがあることをアサーションルーレットという
どのアサーションが失敗したのかがわからない
何のテストをしているのかがわからない
テストごとに分離性を高める
- テストのアンチパターン
テストコードは上から順番には実行されない
→依存関係を減らすため
テスト間に依存関係を作らない
単体で完結するようにする
テストを分散並列実行できるようにする
// TODOリスト [ ] 数を文字列に変換する [ ] 1を渡すと文字列"1"に変換する →仮実装 [ ] 2を渡すと文字列"2"に変換する →三角測量 ただし、 [ ] 3の倍数のときはFizz 仮実装→実装 [ ] 5の倍数のときはBuzz 明白な実装 [ ] 3と5の両方の倍数のときはFizzBuzz
...3年後 このコードを書いた人が辞めて引き継いだと仮定する
振る舞いはわかるけど仕様はわからない
TODOリストはあくまで個人的な備忘録なので残ってない
・テストコードに仕様を残すやりかた
1.メソッド名に書く
public function __仕様文_テスト名()
2.ファイル内でクラスに分割する
class 3の倍数のときはFizz { function test_xxx { } }
テストする内容を構造化(ツリー化)する
冗長なテスト名を構造化して省く
このときにテストの粒度が一定ではないことに気づく
// TODOリスト [x] stringfyメソッドはintを文字列に変換する [x] その他の数のときはその数をそのまま文字列に変換する [x] 1を渡すと文字列"1"に変換する →仮実装 [x] 2を渡すと文字列"2"に変換する →三角測量 [x] 3の倍数のときはFizz 仮実装→明白な実装 [x] 5の倍数のときはBuzz 明白な実装 [ ] 3と5の両方の倍数のときはFizzBuzz
・この重複に意味はあるのか
前任者は深い意味がないことを知っている
1を渡すと文字列"1"に変換する
2を渡すと文字列"2"に変換する(三角測量のためだけに作った)
1と2に論理的な重複がある
引き継いだ人はテストコードを消す度胸はない
テストコードを最小にするのが保守としては楽
品質保証の点からはもっとテストを書く
境界値などのテストを書くと良い
6などの適当に選んだ数字ではなく101などにする
質問
Gitのコミットの粒度について
細かくコミットして後でrebaseする
緑のタイミングでプッシュするコードレビューでどこを見るか
テストコードの結果を見てまずレビューするDBは本物を使うか。モックを使うか。
基本的に本物を使う
モックを使うとモックのメンテに疲れる
fukabori.fmでも話されている
- ドキュメントの話
参考書籍
- 作者: 本多勝一
- 出版社/メーカー: 朝日新聞出版
- 発売日: 2015/12/07
- メディア: 文庫
- この商品を含むブログ (5件) を見る
『日本語の作文技術』
最近たまたま買って読んでいるので、
個人的な感想を書くと読み手にとってわかりやすい文章を書くことにフォーカスした本で、
ドキュメント書いたり、英語のドキュメントを翻訳するときとかに役立つと思います。
感想
TDD本もそうだがライブコーディングを通して TDDで実装していく過程(どこに注目するのかなど)を追体験できることがよかった。
特にViewにロジックを書いてはいけないということはわかっていたけど、 なぜ書いてはいけないのかと聞かれると「わかりにくくなるから」とかその程度の理解だったので、 「Viewをテストすることは難しいのでロジックを分離してテストを容易にする」という無意識でなんとなく理解していた(理解しているつもりであった)部分を言語化できたことは学びであった。
「明白な実装」「三角測量」「将来の読み手を考えたテスト」など、よく考えるとTDD本に書いてあることだと気づいた。 TDDBCに参加した後で再読すると新しい発見があると思うので、もう1度写経する必要があるなと感じた。
後半のペアプロ
TDD Boot Camp(TDDBC) - TDDBC仙台03/課題
第一言語をPHPにしたけど、第二言語のGoになってしまった。Go歴1ヶ月のほぼ初心者。
ペアで組んだ方は気さくに話せる優しい方でよかった。
Goはクラスがないので構造体の初期化時にエラーを出す方法とか、そもそも文法がわからなくて悩んだ。
自分の場合、業務では設計とか実装でわからない部分があると、後で修正する大変さを考えてしまって、
どうするのがベストなのか悩んでしまって手を動かさないまま時間を浪費することが多い。
しかし、TDDで実装を進めると後で修正できるという保険があるので推進力が生まれると感じた。
ペアプロをして、この書き方でいいのかと悩みながらも手を動かしながらグイグイ進んでいく感がよかった。
引数だけ変えてテストするときの冗長を回避する「パラメータライズドテスト」を知った。
PHPUnitにデータプロバイダがある
https://phpunit.readthedocs.io/ja/latest/writing-tests-for-phpunit.html
全体通して
学びが多かった。
自分で参加登録したくせに、いつもの如く前日から不安になってきて、
当日の朝に「体調不良で休もうかな...」とか思ってましたが参加してよかったです。
スタッフや講師の方のおかげだと思いますが、会場の雰囲気的に心理的安全姓が高かったと思う。
懇親会ではt_wadaさんとお話する機会があって、お聞きしたかったことや個人的に悩んでいたことを話せて、他の方もアドバイスをしてくださったりしてよかったです。
TDDBC 札幌を運営してくださったスタッフの皆様、参加者、講師のt_wadaさんありがとうございました!