TDD Boot Camp の参加報告とか読んで

TDD Boot Camp には行っていないんだけど、参加者のエントリを色々読んで触発されたので思っていることをちょこっと書いておきます。
日曜日は id:a-hisame に無理言って色々と聞いた*1しね!
以下引用が多くて微妙に長文。

アクセス修飾子

  • デモ:coberturaに機能追加する*1
    1. テストできそうな箇所を小さい範囲にメソッド抽出
    2. さらに、副作用がある箇所をprotectedメソッドに抽出
    3. サブクラスで副作用メソッドをオーバーライドして無効化
    4. テストのために、検出用変数をprivateからpublicに変更
    5. 検出用変数にアクセスして、assertを記述



*1: この辺ちょっとうろ覚え。もし間違っていたらご指摘ください。

TDD Boot Campに参加しました - @ikikko のはてなブログ

これの 4 と 5 なんだけど、個人的には package private でいいんじゃないかな?と思う。
パッケージ単位ってそこそこ扱いやすい粒度だし、もしそうじゃないならそのパッケージに詰め込みすぎってサインだとも考えられるし。

最後に質問が出ましたが、テストのためにprivateをpublicに変更した場合どうするかという話で、リファクタリングを(おそらく複数サイクル)行い、再びprivateになるようにコードを改善する、という答えを頂きました。

TDD Boot Camp体験記 - Logic Dice

ふむ・・・
つまり private に戻す段階で、検出用変数にアクセスするテストを削除する、ということだと思うんだけど、ここですよね。
package private にしておくと、削除しなくてもコンパイルはでき、テストも Green のままに保てるけど、「public から private に戻す」というステップを踏まなくなる。
すると、テストは内部の実装に依存したままになり、後々まずくなる・・・かもしれない。
うーん、ここら辺はもうちょっと考えないといけないかな。


あとは削除対象のテストが上位の (検出用変数にアクセスする必要のない) テストでカバーできているということをどう判断するか、かな。

テストコードのリファクタリング

「テストは分かりやすく書く物であるが、同じような処理をメソッド化しても良いか」という疑問をぶつけて見ました。
ここでの同じような処理のメソッド化とは、上のLRUキャッシュの例であれば、複数のデータを纏めてput出来るような可変長引数をとるメソッドを作ってもよいのかという点です。
結論から言えば「メソッド化するべき」です。そもそも、テストに対してもリファクタリングを行う以上、このような結果になります。
ただし、そのメソッドは明瞭で単純で無ければなりません。また、コレクションなどの処理を行う場合にたまたまテストが成功していないか(例えば、ループを書いているが全くループせず空の要素を作ってしまい、その結果テストが成功する)ということに注意を払う必要があります。

TDD Boot Camp体験記 - Logic Dice
  • テストコードも、プロダクトコードと同様に無駄なコードは書かないこと。重複を消す。DRY。コピペ禁止。で、見通しがよくなる。

Mapを扱うお題だけどvalueの値自体はテストに関係ないので、put(key, key)みたいなヘルパーメソッドを作る

TDD Boot Campに参加しました - @ikikko のはてなブログ

テスト用の単純なヘルパメソッドはよく作るんだけど、これはいいのかな?と思っていたので一安心。
プリミティブ型の可変長引数受け取るメソッドから、実際に必要なオブジェクトを格納するコレクション生成したりとか、よくやります。

// C#
static IEnumerable<Hoge> Hs(params double[] hs)
{
    foreach (var h in hs) yield return new Hoge(h);
    // 最近は
    // return hs.Select(h => new Hoge(h)).ToArray();
    // みたいなコードを書くことも
}

みたいな。

しばらく Red なゴール

「シナリオテストをTDDの最初に書いてもいいのか」という点です。
〜略〜
結論としては「良い」という意見を頂きました。もちろん、そんな理想系が最初から通ることはあり得ないので、@Ignoreアノテーションをつけるなどして必要な時まで無効化しておく必要はあります。こういったクラス作成の「ゴール」を決めておくことは非常に有用です。
ただし、それがただ1つのゴールではありません。リファクタリングなどによって、ゴールが形を変えてしまうかも知れません。その時には、その変化を受け入れる必要があります。

TDD Boot Camp体験記 - Logic Dice

あらかじめ条件が明確化されているものについては、受け入れテスト的な「しばらくはREDなゴール」を書いてもよい。また、開発を進める中でそのゴールを修正してもよい。

NUnit では、そう言う「しばらく Red なゴール」に対して、Explicit 属性を付けておくと良いのかな、と思った。
Explicit 属性は「明示的にそのテストを実行しない場合に無視される」属性で、例えばプロジェクト全体をテストする場合は無視される。
ただし、Ignore とは違い、「そのテストをピンポイントで指定した場合はテストが実行される」ので、Ignore にしておくよりもこういう「しばらく Red のテスト」にはいいのかな。
Ignore は切り替えが面倒だけど、Explicit は自分でコントロールできるので。

時間のテスト

時間関係のテストはFakeを使って、テストをやりやすくする

TDD Boot Campに参加しました - @ikikko のはてなブログ

時間というものをテストするということで頭の中に警鐘が鳴り響きました。
レガシーコード改善ガイドにおける、「単体テスト」、すなわち早いテストをすることが難しいからです。
〜略〜
ではどうするのか。
頭の中で数分考えたのち、ブログで前にほとんど同じことを教えてもらったことがあった*13ことを思い出しました。



*13: 参考: http://d.hatena.ne.jp/YokoKen/20081027/1225071710

TDD Boot Camp体験記 - Logic Dice

時間とテスト - Logic Dice
の当たりの話ですね。
DateTime.Now とか便利だけど考えさせられるというか・・・

TDD と 開発環境

  • 環境周りを整備すればよかった
    • 最低、リポジトリ用意
    • もっと言えば、CIも用意
      • CIとの連携を手軽にやるならMavenになるけど、知らない人にはハードル高い
      • 演習のレベルではあまりCIのメリットがないかも*2



*2: プロダクトクラス・テストクラスそれぞれ1つずつだから、コンパイルエラーやテスト漏れもまず起きないだろうし

TDD Boot Campに参加しました - @ikikko のはてなブログ

Git と Hudson 連携させると超便利!
ただ、コミットのタイミングはちょっと迷う。

  • 固定値を返すだけの実装を通した後でコミットする
  • それを失敗させるようなテストを書いた上で、それを Green にしてからコミットする

うーん、まぁ 2 つ目かなぁ。
Git の場合は 1 つ目の段階でもコミットしておいて、あとで git rebase -i origin で squash してしまえるから便利!

入出力の網羅性

  • 自分を含め、初心者はテストコードを沢山書いてしまう。
    • 明らかにGREENになるのがわかってるのに書いてしまう。
    • たぶん、これは通過儀礼。

素早くテンポ良く回すということを心がけていくので、特にテストの組み合わせについて網羅性を重要視する必要性はあまりありません*8。むしろ、自分の間違いやすい癖を見つけ、そこを重点的にカバーしていくなど、ある程度自分の経験に基づいた判断をしてもいいという話があったと思います。



*8:個人的には、このあたりも作成するクラスのコンテキストに依存するかなと思う。また、TDDのテストとは別にテストを作ってもいいわけだから、厳密なテストと軽快なテストに分けてもよいと思う。ただし、厳密なテストが軽快であるなら、それをTDDのテスト、すなわち繰り返し実行されるテストとして後から組み込んでもよいのではないだろうか

http://d.hatena.ne.jp/a-hisame/20091220/1261342174

単体テストユニットテストと聞くと、どうしても入出力を網羅したテストを考えがちだけど、そうじゃない、そうじゃないんだ。
ということ。
ここら辺は名古屋 Ruby 会議 01 で id:t-wada (和田さん) と色々と話したときも話題に上ったんだけど、「テスト」という語感からどうしても「品質保証のためのテスト」を思い浮かべてしまう、というのがあると思う。
そうじゃなくて、TDD のテストは「開発者のためのテスト」であるという考えが重要だと思う。


そこでテストという名前と今までの道具をあえて捨てて、新しい言葉、新しい道具で TDD を再スタートさせたのが BDD・・・という理解。
つまり、TDD も BDD も同じものだよ!という。


でも、「通過儀礼」という考え方は無かったなぁ。
入出力を網羅したようなテストを重視する TDD は、完全に TDD とは別物と考えてたんだけど、そうか、そう言う考え方もあるのか。
問題は、それを正しい方向に向かわせてくれる人に出会えるかどうか、ですね。
そう言う意味で (参加してないけど) こういうイベントがもっとあるといいですね!


・・・と、ここまでが (参加してないけど) イベントの感想とかです。
で、以下は TDD に対する疑問。

TDD で言語に用意された assert って書くの?書くとしたらいつ?

つまり、DbC との兼ね合いはどうなるの?ってこと。
兼ね合わせる必要がそもそもあるのかどうか、あるとしたらどうやって兼ね合わせるのか・・・

ドキュメンテーションコメントは書くの?書くとしたらいつ?

疑問です・・・
書くとしたらリファクタリング後、ってことになるのかなぁ・・・

最後に

次あったら是非行きたいなぁ・・・

*1:なんだかこのあと修羅場が続くようなんだけど、鍋に誘った