チラウラヤーン3号

その辺のプログラマーのチラ裏です。

TDD Boot Camp 2020 Online #1 基調講演/ライブコーディング 視聴メモ


TDD Boot Camp 2020 Online #1 基調講演/ライブコーディング

tddbc主催、t_wadaさん による 「TDD Boot Camp 2020 Online #1 基調講演およびライブコーディング視聴」 に参加しました。 これは当日取ったメモです。

※ 独自解釈と感想が入り乱れているため、動画を見ないとなんのことだかよく分からない内容だと思います。


世の中には一発で「ちゃんと動く綺麗なコード」を書く人たちがいる。
そんなことは凡人にはできない。しかし、プログラマはすべからく「ちゃんと動く綺麗なコード」を書くべきなのである。
そこで活用されたい手続きが TDD である。
つまり、TDD の目的は「ちゃんと動く綺麗なコード」を実現することである。
なお、「綺麗な」とは「簡潔で読みやすい」とする。

仕様書を読み取りTODOリストを書いていく

  • red, green, refactoring のサイクルを進める前に、箇条書きでテストケースを列挙していく
  • テストが難しいなら → 分割する
  • 分割できたら「うまみのあるところ」を優先的にやると吉。

テストをやるべき対象

  • テスト容易性: 高
  • 重要度: 高

の順で、その中でも

  1. 正常系
  2. 準正常系

の順にテストを書いていく。わかりやすい正常系のテストは書きやすい。

スタートラインに立つための準備, 土台づくり

設計的要素があり、骨が折れるがここを固めておくと後で楽。 満たすべき仕様をテストケースの箇条書きに直していく。

  • ついでに仕様の表現の揺れや粒度で気がついたところを揃える
  • テストケースの名前は XXXTestにする (最近のトレンド)
  • テストケース名は可能なら日本語にすると良い

テストケースの実装ブロックの並び

  1. 準備 Arrange
  2. 実行 Act
  3. 検証 Assert
  4. 後片付け

"4 Phases Test" とも呼ばれる。 4の後片付けはフレームワークの仕組みを利用することが多いので、基本的には 1-3 を書くことになる。 1-3のAをまとめて "3Aパターン" と呼んだりもする。

TDD では下から書いていく (ゴールから書いていく)

3A を上から書いていくと、ようやっと検証を書く段階になってから

「はて、何を検証したいんだっけ??書き始める時はイメージがあったのだが...」

と道に迷ってしまうことがあるある。そうならないように、 assert から書いていく。

テストを書き始める時または書き進めていく中で手が止まったら?

早めに手が止まるのは悪いことじゃない。いいこと
↑早めにまずい状況が得られた、ということだから。後になって押し寄せるより格段にマシ。

手が止まったら → 具体化すること

例:

  • 「数を文字列に変換」
    • 具体化 → 「1を渡すと文字列1を返す」
      • つまり、抽象度を下げている

「うまくいってないことがある」ということが認識できることが大事。
うまくいってないのに、それが認識できてない場合がある。それはまずい。
認識できるようにするためにも、具体例を求めざるを得ないTDDは有効。

TDDを進めるにあたって

  • テストコードから書いていく。機能実装コードはその後。
  • まだ実装していないメソッドも「先に使って」テストを書く。
  • 「作る前に使う」
    • 「使う側の気持ち」に先になれる
      • API デザインが良くなる
  • テスティングフレームワークの expected と actual の順を押さえておくことは意外と大事。
    • エラーメッセージを読む時、毎回、困らなくて良い

容易性が高いものから取り組む理由

  • ゼロからやることは設計的。
  • 「設計的な作業」いうことは「重たい作業」ということ。
  • スラスラ書けるテスト容易性の高いものを例題として扱うことで、設計面に注力できる → 1つのことだけに集中できる

他人を疑う前に自分の行動や成果を疑え

「意図せずテストが通った/通らなかった」こういう場合、「本当に対象のテストが動いてるのか?」と不安になることがある。

  • 欠陥挿入 (defect injection): 動かない壊れたもの (存在しないメソッドとか) を入れることで「ちゃんと失敗する」ことを確かめる
    • 変異検査 (mutation testing): パスしそうもない値に assertion を変えることで「ちゃんと失敗する」ことが確かめられる
  • if を逆にする、という手法もある

テストを書いていく

  • 仮実装: ベタな値を返す (t-wadaさんが「茶番」とネタにしていたやつ)
    • テスト側である値を期待するテストを書く。
    • テストコードの内容は例えば assert(1, func()) となっているとする。
      • この場合、func() の実装として、 return 1 だけを書いておくもの。本来ならこの実装は何かしらの処理過程を経て、任意の入力に対して対応する出力が返され実装になっているべきなのだが、「テストコードを書いた後、最速でテストを通す」(Red -> Greenの最初のサイクル) を実現するために、仮実装をすると有効な場合がある。
    • なお、「三角測量」としてこの後に追加するテストケースには assert(2, func()) と書かれた場合、テストを実行すると失敗するようになる。そこで、初めて本実装が始まる。
  • 一次変数を持たなくて良いものはインライン化してすっきりさせる
    • bar = func()
      assert(expected, bar)
    • → assert(expected, func())
  • 1テストケース内にテストアサーションを縦に並べる
    • アサーションルーレットと呼ばれるアンチパターン
      • 前段のテストがコケた場合、後続のテストが実行されないため、後続のテストの成否が不明なままになる → 検証部分の独立性が低くなる
      • テストコードの量が増えた場合、並行、並列実行ができない
    • ただし、E2Eテストの場合、Webサーバーやブラウザエンジンの起動オーバーヘッドがあるので、アサーションを列記することを許すことが現実解になる。
  • テストのリファクタ
    • 重複の手直し → 2アウト派と3アウト派に分かれる
      • 3アウト派: 重複が偶発的なものかもしれないからもう一つ様子を見よう
    • 簡潔さを目指そう
    • テストのリファクタリングも小さく小さく進めていく
    • 予想通り失敗させたりしながら、手戻りや勘違いを防ぎ、確実に進めていく
      • 予想通り失敗する
        • 状況がコントロールできていることを確認できる
        • 路面をしっかりとグリップできていることを確認している

テスト実装3パターン = TDDの3つのギアで歩幅を調整

  • テスト→仮実装→三角測量→実装
    • 小さな歩幅
  • テスト→仮実装→実装
  • テスト→明白な実装
    • テストケースを追加するときなど、機能を満たすために次に実装すべき処理がわかり切っている場合、仮実装も三角測量もスキップして実装
    • より「大股で進む」高いギア

TDDのサイクル

1.次の目標を考える 2.その目標を示すテストを書く 3.そのテストを実行して失敗させる(Red) 4.目的のコードを書く 5.2で書いたテストを成功させる(Green) 6.テストが通るままでリファクタリングを行 う(Refactor) 7.1~6を繰り返す

テストコードは「動く仕様書」であって欲しい

  • 仕様と同じ構造になっている
  • 粒度が揃っている
  • 必要最低限の量
  • 「このテストケースは何のために書かれているの?」が理解できるように

その他

  • 抽象度を下げたら後で戻す
    • 三角測量が他のテストケースにもないのであれば、粒度を揃えるために役目を終えた後は消しておこう
      • 自分がいなくなった後に引き継ぐ人のことを想像して、その人が「消す判断がつかない」コードは消しておくこと。
      • テストもメンテナンスが必要 → 必要最小限が望ましい。
      • 「テストを減らす」のはテストを書いた本人しかできない。
        • → 本人がいる間に、消せるものは消しておこう
      • 「理解容易性」が低いテストは最も避けたいもの ← 三角測量である意図明確でないテストコード
        • → 不良資産になる
  • テストメソッド名を「仕様___具体例」に直す
  • 仕様がネスト構造を持っていれば、テストコードも入れ子にしてブロック構造を持とう
  • リファクタリングはいつまでもやれてしまう
    • ので、5-10minやったらやめる ... 案1
    • 重複を除去できたら終わる ... 案2