テストカバレッジはどこで崩れたのか?TDDでコードを守る実践ガイド
コードを書くのは楽しいのに、そのテストを書くのは億劫——そんなふうに感じたことはありませんか。「1行だけのメソッドなんて、本当にテストが必要?」と考えてしまうのも無理はありません。あまりに些細に見えますし、テストを追加すれば開発時間は2倍、3倍になります。しかも次にコードを変更するときは、テストも一緒に直さなければなりません。見積もり残り時間がわずかなときには、特に「無駄」に思えてしまうものです。
しかし、そうしているうちにテストカバレッジは20%まで落ち込み、コードへのあらゆる変更が「カードタワーの中段を、倒さずに入れ替える作業」のように感じられるようになります。どこかで何かが間違っていたのです。当時は正しい判断だったはずなのに、気づけばメンテナンス不能なコードベースが出来上がっていました。
どうしてこんなことになったのでしょう?本来なら、テストは安全網として機能し、自信を持ってリファクタリングできるようにしてくれるはずでした。コードをより良くしてくれる助けになるはずだったのです。ところが現実には、もう理解できなくなったコードが低いカバレッジのまま放置され、せっかくあるテストがむしろコード変更を難しくしている——そんな状態に陥ってしまいます。
これはスキルの問題ではありません。優れた開発者でも起こりうることです。原因はプロセスにあります。新機能の書き方を少し変えるだけで、テストに足を引っ張られずにコードを守れるようになります。テストはコードをより理解しやすく、より柔軟にしてくれる存在へと変わるのです。すべてのコードパスがテストされているという確信を持って、安心してコードを変更できるようになります。
「テストするかどうか」を選ばなくて済むようにする
キーボードの前で「このコードにはテストが必要だろうか」と判断しようとしている時点で、すでに間違った道を歩んでいます。答えは常に「テストする!」がデフォルトであるべきです。どんなに些細に見えても、テストを書きましょう。
書くのが簡単なコードは、テストするのも簡単なはずです。そして複雑なコードほど、「書いた直後」ほど簡単に見えることは決してありません。6ヶ月後にそれがまだ些細に見える保証は、どこにあるのでしょうか?
とはいえ、テストしすぎてもいけない
巨大なテストスイートは、それ自体が問題になりえます。実行に20分かかるテストは、実質的に「テストがない」のと同じです。毎回実行することはできないからです(「やると言うでしょうが、実際にはやりませんよ」)。さらに悪いことに、壊れやすいテストが多すぎると、リファクタリングは以前よりも苦痛になり、結局やらなくなります。そうなれば、長編小説並みに長いメソッドが生まれてしまうのです。
これは先ほどの主張と矛盾していませんか?必ずしもそうではありません。鍵は、テストをコードの実装ではなくインターフェースに集中させることです。例を見てみましょう。
class Cart
def initialize(item_params)
@line_items = Array(item_params).map {|item| LineItem.new(item[:name], item[:price])}
end
def total
@line_items.sum(&:price)
end
end
このコードにはCartクラスとLineItemクラスの両方に対するテストが必要に思えます。しかし、LineItemクラスは他の場所で使われていますか?もし単なるCartの実装詳細であり、外部に公開されていないのであれば、果たしてどれだけのテストが必要でしょうか?Cartクラスを通じてテストすれば十分ではないでしょうか?
リファクタリングによって抽出されたクラスは、しばしば専用のテストスイートを必要としません。それは単なる実装詳細だからです。そのクラスが単体で使われるようになったとき、初めて追加のテストが必要になります。
公開インターフェースに対する堅牢なテストスイートがあれば、テストを書き直さずに実装を自由に変更できます。しかも、オブジェクトの実装に対して「そこそこ」のテストスイートを書く場合より、はるかに少ない労力で済むのです。
TDD(テスト駆動開発)でテストコストを分散させる
最初のセクションでは「すべてをテストすべき」と学びました。次のセクションでは「公開インターフェースだけをテストすべき」と学びました。この一見相反する2つの目標をつなぐのが、テスト駆動開発(TDD)です。
TDDでは、次のプロセスに従ってテストがコードの設計と実装を駆動します。
- 必要なコードがすでにあるものと仮定して、失敗するテストを書く。
- そのテストをパスする最もシンプルな実装を書く。
- 重複を取り除く(あるいはコードをより表現豊かにする)ためにリファクタリングする。
- テストを再実行し、まだパスすることを確認する。
- ステップ1に戻る。
この手順に従えば、すべてのコードがテストされ(失敗するテストなしにコードを書かないため)、同時に公開インターフェースだけがテストされる(リファクタリング直後に新しいテストを書かないため)ことになります。
もちろん、現実はそれほど単純ではありません。それでも、最も複雑なコードでさえTDDで駆動する方法はあります。
TDDには、次のような副次的なメリットもあります。
- より柔軟で、テスト済みのオブジェクトモデルが手に入る(これこそが最大のメリットと言えるでしょう)。
- システムが定義上テスト可能になるため、将来のテスト作成コストが下がる。
- テストコストが開発プロセス全体に分散されるため、見積もりがより正確になる。
- 次に何をすべきか迷うことがなくなり、フロー状態を保てる。
では、どう始めればいいのか?
始めることが一番難しいのです!一度テスト駆動でコードを書くリズムに乗ってしまえば、もうやめられなくなります。
次に新しい機能に取り組むときは、上記のTDDの手順を試してみてください。最小限の労力でほぼ100%のコードカバレッジを達成でき、堅牢な土台の上に構築できるようになります。来年にコードを変更するときも、あるいは今日の午後、要件がまた変わったときでも、テストスイートが自分を守ってくれるという完全な確信を持てるでしょう。
取り組んだ結果が出たら、ぜひ筆者にメールで経過を報告してみてください。
シンプルなテストプロセスに従うことで、些細な判断やバグ追跡に費やす時間を減らし、顧客とビジネスのニーズを解決するコードを書く時間をもっと増やすことができます。
-
Macのエラーコード36とは?原因と8つの解決方法を徹底解説
Macは、デスクトップでもノートブックでも最高クラスのCPUパフォーマンスと充実した構成で知られており、その優れた機能は多くのユーザーから高く評価されています。重要なファイルやフォルダの移動からコピーまで、Macではすべてがスムーズに行えます。しかし、まれにファイルの移動中にエラー36が発生することがあります。同じ問題でお困りの方にとって、この記事は解決策を見つけるのに最適なガイドです。本記事では、Macのエラーコード36とは何か、そしてその具体的な修正方法について詳しく解説していきます。Macのエラーコード36とは?エラーコード36は、Macユーザーがファイルを移動、コピー、または削除しよう
-
Windows 10のテストモードとは?入り方と解除方法を徹底解説
Microsoft Store以外からアプリケーションをインストールするたびに、警告ポップアップが表示された経験はありませんか?これは、外部ソフトウェアがユーザーの知らないうちにシステムに被害を与えることを防ぐためのセキュリティ機能です。同様に、Microsoftは各種ドライバーやアプリケーションに悪意のあるコードが含まれていないかを検証し、安全であることを証明するデジタル署名を付与しています。検証されていないソフトウェアや認証証明書を持たないソフトウェアは、Windows OSによってブロックされる可能性が非常に高いのです。 こうしたセキュリティ対策はパソコンを安全に保つために重要ですが、開