日々常々

ふつうのプログラマがあたりまえにしたいこと。

テストと言うパートナー #TddAdventJp

TDD Advent Calendar jp: 2011の 12日目です。

テストはパートナー

「何を言ってるんだ?」な感じかもしれませんが、私にとってテストはパートナーです。
私がTDDのコンテキストで言う「テスト」はDeveloperTestです。このテストは開発者の開発者による開発者のためのテストであり、つまり開発者たる私のためのものです。私だけのためにテストは働いてくれます。

テストに対する不安

TDDや自動テストと言う言葉に触れ、「いざテストを書こう」と思った時。もしくはよく知らないままテストコードを書かなければならなくなった時。テストに対して不安を感じると思います。TDDは「不安をテストにする」とか言いますが、そもそもテスト自体に不安を感じるようでは本末転倒に思えるかもしれません。私も昔はテストなんて信じていませんでした。テストコードを書いても、所々で System.out.println*1 を行ったり、デバッガを使用して変数の動きを確認したりしていました。目で見ないと納得出来なかったのです。
「目で見ないと納得出来ない。」多くの人に共通するものだと思います。目で見て確認する事のデメリットは、主に時間がかかること、間違えること、そして再現が難しい事です。そんなことは解っていても、目で見ないと納得出来ない。よくわかりますが、これは「全部自分で抱えてしまう」のと同じなんです。全部抱えるのは大変です。テストを信じられれば、その部分をテストに任せる事が出来ます。
「それが信じられなくて困っているんだ!」もっともです。テストを信じられないのは何故でしょうか?簡単な話で、よく知らないからです。よく知らないのは、あまり関わっていないから。これは人に置き換えても同じです。普段関わっていない人にいきなり全幅の信頼を寄せられるでしょうか?私には無理です。信頼出来る人からの紹介ならば信じてみようとは思うものの、それでも不安は残ります。しかし頻繁に交流を持っている人ならばどうでしょう。相手の事を知っていれば、どの程度の事を任せられるかもわかるものです。
テストを信じるためには、とにかく触れ続けることです。TDDでは常にテストが付き纏います。うざいほどにいつも一緒です。そんななので、嫌でも「テストがどうするか」は解るようになってきます。テストの事が解ってくれば、テストを信じても良いかなと思える事でしょう。
私が System.out.println をせずに assert で済ませられるようになったのは、TDDを始めてからだと思います。今では目で確認する方が信用出来ない状態です。テスト依存症かもしれません。

テストを信用するために

信用したいと思っても、何をしでかすか解らない人をいきなり信じるのは愚策です。テストとの付き合い方を身につけましょう。小さい事からお願いしてみるんです。必ず自分の思った通りの結果になるテストを書きます。
このときには必ず失敗するテストから始めます。テストに「失敗しろよ」とお願いして、失敗するのを見届ける。失敗するように書いたのですから、当然失敗するのですが、ここでは予想通りに失敗することを確認します。予想通りである事がポイント。思った通りに動くものは信用出来るものです。
成功ではなく失敗から始める理由は、JUnitなどのテスティングフレームワークの最も重要な機能が失敗の通知だからです。テストは失敗を通知出来なければなりません。しかも派手に。画面全体が真っ赤に染まっても良いくらいだと思います。染まりっぱなしは困りますが。
テストへの小さなお願いが見事に達成されたなら、信じるための第一歩が踏み出せた事になります。早速次のステップに入り、テストを成功させましょう。予想通りの成功を確認出来れば、また少しテストが信じられるようになるはずです。信用は積み重ねなければ得られません。テストが思った通りに動く事に注意を払いながら、一歩一歩踏みしめて行ってください。テストはきっと応えてくれる事でしょう。

テストが頼りになる瞬間

テストをある程度信じられるようになり、心を許し始めた頃、きっとテストは貴方の予想を裏切ります。何も今まで出来ていた事が急に出来なくなるわけではありません。落ち着いてみれば、思いがけない不具合をテストに指摘されている事に気付くでしょう。影響範囲の見極めを誤ったか?要らない変更を行ってしまったか?原因は定かではありませんが、とにかくテストが問題を教えてくれた事は確かです。これは一人では気付く事が出来なかった事で、今までの「思い通りに動く」とは異なります。私が一番「テストを書いていてよかった」と思う瞬間はここです。
単純に思い通りに失敗や成功を通知するだけでは、テストを書く意味は薄い*2です。テストを書き続けた事が報われる瞬間は、テストに思いがけない誤りを指摘をされたときです。この状況になるまで、多くのテストコードを書く必要がありますし、書いてきたテストが元気な状態でなければなりません。テストとの信頼を築くのは一朝一夕には行かないのです。TDDはテストの育てゲーですので、大切に育てましょう。

テストが開発者にしてくれること

テストが自立し始めると、開発者に色々な事をしてくれます。例えば現実を突きつけるとかです。私は毎日のようにテストに「あ、また間違えた」とか言われてます。でもそれは私とテストだけの秘密です。他の人にはバレていません。テストのお陰で、あまり恥ずかしい思いをせずに済んでいます。
私はあまり記憶力の良い方ではありませんので、思えれば済むだけのメソッドの引数の意味とかを良く忘れます。なので開発中にこんなテストを書く事もしばしばあります。*3

@Test
public void testSubstring() {
    assertThat("abcdef".substring(3), is("def"));
}

ことTDDにおいて、テストはすぐそこで、いつでも動けるように待機してくれています。それはプロダクトコードのテストだけではありません。例えばライブラリの使い方が解らなかったり、演算子の挙動をど忘れした時とかにも、そっと手助けしてくれます。人に聞けば馬鹿にされるような質問でもテストは笑わずに答えてくれます。テストが信じられるようになれば、あらゆる不安にテストと一緒に立ち向かえます。いつでも相談出来る相手がいる状況は、孤独な戦いに挑まざるを得ない開発者にとって大きな心の支えになる事でしょう。

そんなわけでパートナーです

ソフトウェア開発は手ごわい。問題は常に複数ある。問題は常に絡み合っている。
それらと徒手空拳で戦うのは非常に厳しいものです。幸いにして開発者はコンピュータの力を借りる事が出来ます。コンピュータの中には様々な協力者候補がいますが、テストは開発者と一対一で付き合ってくれる良い相棒になります。
テストは頼んだ事を忘れませんし、仕事は非常に迅速です。そして非常に厳しい。全く妥協してくれない。もう少し穏やかになってくれてもいいのになとたまに思ったりもしますが、結構癖になる厳しさでもあります。
TDDはテストとのペアプロだと思います。テストと対話して行う開発スタイルです。テストを随時書いていれば、テストの持つ知識は「テストを書いた開発者が最も詳しい瞬間のスナップショット」になります。自分の一番強い瞬間を記録して、それと一緒に戦えるんです。ゲームだと裏技級ですね。やらない手はありません。
新しくコードを書くとき、リファクタリングするとき、私はテストに相談します。テストは生暖かい目で「やってみれば?」と促すのが常ですが、尋ねる事で再考の機会を得られますし、やってしまえば今まで書いたテストが評価してくれます。しつこいくらい何度も何度も確認する私に、文句一つ言わずに付き合ってくれるテストが居るから、私はそれなりにプログラムが書けている。そう思う今日この頃です。

おまけ:TDDが出来ない理由になってない理由

「テスティングフレームワークの使い方が解らないから出来ない」
スティングフレームワークを知らない事に由来する不安を払拭することすらも、TDDには組み込まれています。簡単なテストを書く所から始めれば、すぐにテスト使い方が解るようになる筈です。難しい機能はその先に。お互い信じられてから、一歩ずつ踏み出していけば良いんだと思います。
「テストが書けるほどその言語を知らない」
知らないなら尚更テストから入ればいいと思います。コンパイルが通る程度の構文を理解していれば、テストは書けます。テスティングフレームワークが無い場合もあるかもしれませんが、xUnitの基本的な構造は非常にシンプルですので、即席で作る事も可能です。即席の場合、機能不足は否めませんけども。
「自分の現場では出来ない」
TDDは一人で行えます。たとえ閉鎖環境*4であっても、プログラムを書く事が仕事である以上、テストは書けます。様々な事に時間がかかるからこそ、自分が書いたコードのフィードバックをいち早く得る必要があります。テストを書く隙はきっと存在することでしょう。必要な時にすぐに呼べるように、普段から仲良くなっておくのが良いと思います。いきなり手伝えと言われても、テストからすれば無理な話です。
「テストには時間がかかる」
適用範囲を誤っている可能性が高いです。私にとってTDDは開発を加速するものです。小さいフィードバックループにより、手戻りが非常に小さくなります。全てが予想通りに進む方には不要ですが、私はうっかりの多いプログラマですので、引数を間違えたりど忘れしたりもしょっちゅうです。そんな詰まらないミスで、デプロイとか画面確認とかエビデンス取り直しとかやってられません。テストコードを書く事で、悩む時間や手戻りの時間をガシガシ削れます。とは言え釣り合わないほどコストがかかる領域も存在します。タイトな時は、時間が削れると判断出来る部分にのみ、テストを適用する。この範囲は習熟度で変化しますが、やればやるほど拡がっていくと実感しています。
「TDDやりたいけど出来てない」
出来てます。どこからをTDDと言うかは個人によってまちまちですが、私にとってTDDの範囲は非常に緩いです。戦士はLv1でもLv99でも戦士だってくらいの広さです。私にとってのTDDは「テストが開発を駆動するか」に尽きます。もっと砕いて言うと、「こうなれば良いな」を思い描いて開発するかです。自動テストが無くても、頭の中で出来うるものを考えてから作っていればTDDに含めてしまっていいと思っています。頭だけじゃ大変なので、テストコードに補助して貰う。そんな感じで自分が担当していた所をテストと分担していけば、自然とテストと一緒に開発する事になるんじゃないでしょうか。

*1:Javaでの標準出力です。

*2:テストを書く事で他にも効果もありますが、ここでは触れない事にします。

*3:Javaが解らない方は「その言語の標準的な機能」とでも思って下さい。あ、これは例ですよ?こないだ List#subList は素で書きましたけど。

*4:ネットに接続出来ない、自由にソフトウェアをインストールできない、など。閉鎖具合はまちまち。