なぜテストコードを書けないのか
なぜテストコードを書けないのか
どちらかというとテストコードを書かなければいけないんだけど... それは分かってるんよ? っていう人に読んでほしいです。
「テストコードを書いたほうがよい」というのはテストコードを書いていない開発者でも認識はされていると思います。「やった方がいいのは分かるんだけど、時間が取れない」「覚えるのが大変そう」「今のコードにテストコードを入れるのが大変すぎる」などなど、やらない理由はたくさん出てきます。(念のため、やらない理由を非難するのが目的ではなく、それを解消していくのがこのブログの目的です)
テストコードを書いて開発している方から見ると、逆だろう! って言いたくなります。しかしこれでは「時間が取れないんです」「やらないから時間が取れなくなるんだろう」と議論が平行線になってしまいます。
私が思うに、頭ではテストコードは書いた方がいいと理解していても、体ではそう思ってないからだと思います。また、「テスト」という言葉のおかげか、進んでやるのではなく、やりたくはないけどやらなければいけないものに分類され、なかなか一歩が踏み出せないのではないかと思います。
テスト駆動開発はまずは成功体験が必要
機会があればテスト駆動開発をやっている人になぜやっているかを聞いてみてください。人によって答えは変わるかもしれませんが、趣旨としては「テストコードを書いた方が開発が楽で早いから」というのが多いと思います。
逆にテストコードを書いていない人の理解は「品質が上がるんだろうけど、テストコードを書くほうが仕事が増え遅くなる」となっているように思えます。理屈ではテストコードを書いた方がいいのがわかっている人でも、感覚的にはこうなっているのではないかと。
テスト駆動開発をしている人としていない人のこの認識の差はとても大きいです。「テストコードを書かないのがいけないんだ」って考えてしまい、テストコードはいやいや仕方なく義務で書くものになります。仮にその状態でテストコードを書いたとしても、大きな効果は得られません。効果が限定的なことを感じ取って書くのを自然にやめてしまうか、それでも突き進んでテストコード自体が負債になることでしょう。
このようなことを避けるために、まずはテストコードを書いてよかった! という体験を得られるようにするのが大切です。
過去にとあるエンジニア仲間から「テストコードって書く時間もったいなくない? やった方がいいのは分かってるんだけど」みたいなことを言われたことがあります。自分は「いや、テストコード最高です。書いた方がいいですよ!」って返しました。そのエンジニア仲間は私のことをとても技術力のあるエンジニアだと誤解していたので、「ほんとに? でもまあ、まーさんがそこまで言うならやってみるよ」と言ってくれました。数か月後に会ったときに「テストコード最高!」「過去のテストコードのないコードをどうやったらいいか悩んでる」「テストコードを書かなかった過去の自分を責めている」とまるっきり変わっていました。
これは、彼が短期間でテストコードを「書いた方が開発が楽で早いから」という体験をしたおかげでしょう。頭だけでなく体でも理解したから、テストコードを積極的に活用していくスタイルに切り替わりました。開発方法そのものが大きく変わったのです。
ここからも分かるように、「テストコードを書くのが面倒そう」から「書いた方が楽」にどうやって変わるかが重要なのです。一言いうだけで変わった人は後にも先にもこの人だけですが、変わればだれでも続けることができます。
テストコードを書かないといけないよなあ、大変だよなあと考えるのではなく、テストコードを書いてよかったと体験することを優先してください。より深い学習や全体に導入するのは後からでいいのです。
テスト駆動開発に対する誤解
自分は何度かテスト駆動開発のハンズオンをしました。うまくいったかはおいておいて、その中で気が付いたの一つが、テストコードを書くのはやらなければいけないものと考えている人が多いということです。
もう一つ気が付いたのが、テスト駆動開発やテストコードを書くことをバグを減らすという品質のためにやると誤解があるなあということです。もちろんその目的もありますので誤解とは言い切れないのですが、別の目的の方が大事です。自分も最初はバグを減らすのだけが目的でしたが、今はそこを重要視しなくなりました。
ですので、最近は「テスト駆動開発をテストだと考えないようにしてください」と言うようにしています。ハンズオンもやめました。ハンズオンだとやり方の勉強会になり、なかなかよさを体験できるところまでいかないからです。その代わり、20 分程度の簡単なテスト駆動開発のデモを見せるようにしています。
デモの中でテストケースを一つだけ書いてそれを実装するを繰り返します。ここは設計がよくないから関数に抽出しますねと説明しながらリファクタリングをします。このリファクタリングはよくなかったですねと説明しながら戻します。この時、見ている人にはテストを実行する頻度に着目してもらってますし、テストコードの書き方とか設計の妥当性とかはあまりに気にしないで、その場の勢いで実装を書き換えまくってることに着目するようお願いしています。
要はテスト駆動開発はテストコードをプログラム設計の試行錯誤の道具として使います。短時間で今までとは比較にならない回数 (二桁くらい変わるかも) の試行錯誤のおかげで、より保守性 (読みやすく、変更がやりやすい) を高めることができます。関数に分けたり変数名を分けたりのような単純なリファクタリングであっても、どれがいいかを頭の中で考えるだけでなく、実際にやってみることができます。リファクタリングを行った際には再テストが必要ですが、テスト駆動開発はそれがほぼ一瞬で完了するので、たくさん試してより良いものを探しやすくなります。
実際、参加された方に「どうでした?」と聞いたところ、「テスト駆動開発って思ってたのと (い意味で) 違った」「これは確かにやった方がいいよね」「まさかこんな短時間に 50 回とかテストを走らせると思わなかった」という意見を頂戴しました。(空気を読んでくれているだけかもしれませんが)
実際のところ「テスト駆動開発はテストではない」わけではありません。もちろん、バグを減らす目的もあります。しかし、短期間でテストを何度も実行できるようになると変更容易性や可読性への改善の道具として使えるようになります。「テスト駆動開発はテスト」と思ってしまうと、試行錯誤ができるというメリットが抜けてしまう方が多いのです。(自分も抜けますし、テストを学んだ方でもなければテストに対するイメージはそういうものだと思います) 最近は「テストコードは開発者が試行錯誤をするための武器の一つ」と説明するにしています。
まずはほんのちょとっとで始めよう!
体験することが大事な場合、どうやって今のプロジェクトにいいのかなどを考えても意味はあまりありません。ここまで説明した通り「テストコードを書いたら楽になった」という体験をすることが大事です。そのためには今の開発に全面的に採用する必要はありませんし、上司や他の仲間の許可も不要です。
始めるのはたった一か所で構いません。ソースコードを眺めてみてください。多分なんちゃらユーティリティという大量のメソッドがあるクラスがあると思います。それを修正するときやメソッドを追加するときに、次の条件に当てはまっていたらテストコードを書いてみてください。可能なら、テストケースを一つかいて実装を書いてとテスト駆動開発のサイクルを試してください。
- データベースやファイルや API を呼んでおらず、純粋に計算しかしていないこと
- 時刻などの変化するものを利用していないこと
- 分岐がそれなりにあること
- 心に余裕があること
テストコードの書き方で最初は苦労するかもしれませんが、このような小さなテストコードの書き方に必要な技術要素はそれほど多くありません。本を一冊最後まで読む必要もありませんので、ぐぐったり AI に聞きながら試行錯誤してください。
うまくいかなくても気にしないでください。ダメだと思ったらコミットせずにテストコードは捨てちゃって構いません。テストコードを書き続ければよくなるんだという思いのもと、うまくいかないまま続けるのは捨てるよりもっと悪いです。
何度かテストコードを書いて修正をしてを繰り返しているうちに、テストコードを書いた方が楽なケースがあることに気が付いてきます。そうすると、自然とテストコードを他でも活用しようと考え始めます。テストコードの書き方を詳しく勉強を始めるのはそこからでも構いません。義務感でやるのではなく、楽ができるからに変わっていれば、勉強も、いやいややらされた学生時代を思い出すのではなく、楽をするために効率のいい学習ができます。
テストファースト!
「テストファーストとはテストコードを最初に書きます」も誤解だと思ってます。テストコードを最初に書くのがテストファーストっていうわけではないし、テストコードを最初に書いたからテストファーストになるわけでもないからです。
そもそも開発において最も工数がかかるのがテストとそのデバッグや修正です。人月の神話によると、工数の半分くらいが目安だそうです。
開発において効率を上げるためにどのフレームワークがいいかみたいな議論がありますが、開発自体はテストよりも時間がかかりません。それよりはテストを効率よくやることの方が開発全体の効率に大きく寄与します。
一例として、POS レジシステムとそれを管理する中央システムで説明します。このレジの注文が正しく中央のシステムに登録されるかをテストします。この時、商品のバーコードを読ませ、POS レジで内容を確認し、現金を投入し、おつりを確認し、それを中央のシステムで即座に確認できるかをテストすることになります。
バグが見つかったらコードを追いかけて探します。修正は一行だけかもしれません。それを直してビルドしてデプロイして、また商品のバーコードを読ませるところから始めます。そして今度は別のバグが見つかりました。溜息しか出ません。
多くの開発現場ではこんな開発方法はやっていません。次のような工夫かもしくは他の工夫もやってると思います。
- POS レジにデバッグモードを用意して、ダミー注文をワンボタンでできるようにする
- POS レジの代わりのフェイクのプログラムを用意して、それで注文をする
テストをやりやすい工夫を整えることで、開発の効率が大きく上がります。この、テストのことを優先して考えて開発することがテストファーストです。コードを書くよりもテストやそのデバッグ修正にかかる時間の方が圧倒的に大きいので、コードを早く書けることよりもテストがやりやすい方が効率が良いのです。
多くの開発現場では大なり小なり何らかのテストのための工夫をしています。ですので、テストコードを書いていないからテストファーストでないいけてない現場だと思う必要はありません。
テスト駆動開発ではテストコードを試行錯誤の道具として使います。手でテストしていても試行錯誤はできますが、回数が一桁二桁違いがでます。テストコードがあると楽ができると体験した開発者は、純粋に計算しかしていないメソッドのテストが最もやりやすいことに気が付きます。そうすると、無理やりデータベースも含めてテストしようとするのではなく、例えば消費税の計算などの計算だけのクラスを作成すると簡単にテストできるように気が付きます。まさに、コードを書くよりもテストのことを優先で考えた工夫です。
「テストコードを先に書く」だけに意識を向けるのではなく、自分らが今までやったテストのための工夫を一度振り返ってみてください。それらをしなければ開発はより困難になってたことでしょう。自分たちがテストファーストをやっていることに気が付くはずです。テストコードを書くこともそれらの工夫と同じ、開発を楽にするためのものです。いやいややらされたものではなく、自分たちで考えた工夫だからこそ役に立つのです。
他業界の例
テスト駆動開発のようなものは他業界でもよく行われています。テストの効率を上げ、試行錯誤の回数を増やすのは大量生産を行う製造業では常に改善され続けています。その一つとして、乗用車の衝突安全ボディの開発を紹介します。(自分は自動車の専門家ではないので誤りはあると思います)
大量生産する乗用車としてフォード T 型から始まった大衆車ですが、今の乗用車と比べるとかなり危ない車です。政府や自動車メーカーの努力のおかげで乗用車の衝突安全性は飛躍的向上しました。
衝突安全基準を守るために、初期のころは実際に乗用車を作成してぶつけて検査していました。検査結果がだめだった設計からやり直しです。ぶつけるのは一瞬ですが、再試験にはかなりの時間とお金がかかります。(ぶつけた車輛を再利用できないのは当然ですが、ダミー人形も使い捨てだそうで、金額がばかにならないんだそうです) 一車種あたり何回ぶつけて試行錯誤していたかまではわかりませんが、100 回試験をすることはできません。試行錯誤はほとんどできませんので、鉄の量を増やし、マージンを取って必要以上に頑丈に作ることになります。
コンピューターの性能向上でシミュレーションでぶつけなくてもある程度再現できるようになりました。実際にどれくらいの時間でシミュレーションが完了するのかはわかりませんが、実車を作る必要もなくなり、以前と比べると圧倒的に短い時間で完了するようになりました。
現代の乗用車の衝突安全基準の達成はかなり難しくなっています。正面衝突でもフルバックとオフセットの二種類があり、フルバック耐性を挙げるとオフセット耐性が下がるそうで、両立はかなり難しいそうです。側面や後方もありますし、メーカーは法律で定められた基準よりも厳しい基準を設けています。
安全基準を満たすだけでなく、鉄の量をより減らすことも求められます。鉄の量が減れば、その分軽くなって燃費が向上し、価格も下げられます。そのためにはライバルメーカーよりもたくさんの試行錯誤が必要です。
これらは昔ではできませんでした。テストの方法を工夫することで、試験にかかる時間を圧倒的に短くし、それを利用して試行錯誤回数を増やして、今までにない高品質を生み出すところはまさにテストファーストであり、テスト駆動開発のようなものだと思います。
余談ですが、シミュレーターは現実と差が出るので、最終的にはテスト車両を実際に作ってぶつけています。面白いなあと思うのは、そのぶつけた結果を再度シミュレーターに戻しているところです。シミュレーションの精度が高ければ高いほど、試行錯誤がやりやすくなるので、この結果を戻すのも重要です。
最後に
テスト駆動開発は実際にその良さを体験しないとわかりません。がっつり使わないといけない、バグを減らすために我慢してやるなどと考えるとなかなか始められず、もったいないです。まずは心にゆとりがあるときにやりやすいところで小さく始めて小さな効果を得てください。そのために上司や同僚の許可は不要です。なんかコレジャナイって思ったらやめればいいのです。
何度かやるうちにその良さが実感できます。それができてから同僚に広めましょう。同僚に浸透し、今までのコードにも入れようねという空気になってから上司に相談するようにします。
うまいテストコードを書くこと自体も試行錯誤で積み重ねです。いきなり全体にテストコードを入れようとするのは積み重ねがない状態でやることになります。苦労するだけでいい結果にならないことが多いです。少しずつやっていきましょう。その代わり、少しずつテストコードの書き方を変えていきます。
また、最近 t_wada 氏がその会社のプロダクションコードを使ったテスト駆動開発の研修をしています。小さな TODO アプリのようなサンプルコードでテストコードを書いてもメリットはわかりにくいです。現状開発者自身が修正で苦労していることを中心にしたテスト駆動開発のデモやハンズオンは開発者の心に刺さります。研修のレポートを見ると「こんなやり方があるのか! これなら自分でもできる」というコメントも見受けられます。
Discussion