自分はソフトウェア開発に携わってきたが、単体テストの重要性を意識し始めたのはごくごく最近のことだ。
以来、単体テストの重要性必要性を強く実感し、今は布教活動に邁進している。
今回は、単体テストについて非常に明確に網羅的に書かれている名著
「単体テストの考え方/使い方」
の内容を元に、なぜ単体テストは必要なのか、単体テストがないとどうなってしまうのかをまとめていこうと思う。
参考文献
Vladimir Khorikov (著), 須田智之 (翻訳) 「単体テストの考え方/使い方」
単体テストとは?
まず単体テストの定義を明確にしておく必要がある。
「単体テストの考え方/使い方」によると、単体テストとは以下のものになる。
「単体テストの考え方/使い方」より:
・「観察可能な振る舞い」の1単位を検証すること
・実行時間が短いこと
他のテスト・ケースから隔離された状態で実行されること
ではここでいう「観察可能な振る舞い」とは何を指すのか?
「観察可能な振る舞い」とは?
プログラムを「観察可能な振る舞い」と「内部的な詳細の実装」とに分けることを考えた場合、「観察可能な振る舞い」とは、以下となる。
・クライアントが目標を達成する為に使う、公開された操作/状態
これに対して、内部的な詳細の実装とは、上記に含まれない箇所を指す。
classでいうとprivateのメソッドなどは「内部的な詳細の実装」となると考えてよいだろう。
そして、テストの対象は、あくまでも「観察可能な1単位の振る舞い」であることが望ましい。
理由として、「内部的な詳細の実装」までテスト対象としてしまうと、単体テストがリファクタリングに耐えられなくなり、単体テストに対するメンテナンスがとても大変になってしまうからだ。
単体テストをやる目的とは?
では単体テストをやる目的とは何か?
ソフトウェア開発のプロジェクトを持続可能なものにする
ということになる。「単体テストがないと何が起こるのか?」を考えると、その理由がよくわかる。
単体テストがないソフトウェア開発プロジェクトは持続不可になる。
では以下の状況下において、単体テストがない環境だとどうなるかを見ていこうと思う。
「動きは変わらないが、もっとこうしたら良くなるという案はある。」
⇒今の動きに問題がある訳ではないし、下手に触ることでリグレッション(動いていたところが動かなくなること)が発生するリスクがあるので、触るのをやめておこう。
「機能追加では、ここまで大きく作りを変えてしまった方が今後の為だ」
⇒テストの範囲が広くなってしまう。単体テストがないので、時間がかかる手動での機能テストをしていくしかないが、時間がない。
今後の拡張性などは一旦無視して、要らぬ不具合を生まぬ為にも必要最低限の変更でこの作業を終えよう。
上記のような方向に進みがちだ。
ソフトウェア開発は度重なる機能追加と不具合対応で膨張していくが、このような場当たり的な対応を続けていくと、ソフトウェアは秩序がなくなり、次第に修正、変更に膨大な時間がかかるようになる。
そしていずれは誰もメンテナンスできない魔窟と化し、ソフトウェアは崩壊の一途をたどる。
ソフトウェア開発プロジェクトが持続不可となる状況というのはこのような状況である。
単体テストが持続可能なソフトウェア開発プロジェクトを持続可能なものにする。
これが、単体テストがある場合だと以下のようになる。
「動きは変わらないが、もっとこうしたら良くなる」
⇒コードを修正しても、修正前と動き自体が変わっていない(リグレッションが起きていない)ことは単体テストで常に保障されるので、思い切って「良くなる」と思える形に修正しよう。
「機能追加では、ここまで大きく作りを変えてしまった方が今後の為だ」
⇒テスト範囲が広くなるが、既存部分には単体テストがあるので、既存部分にバグが混入しないことは単体テストが保障してくれる。単体テストは自動化されているので、実施時間はほぼ0で無視できる。
なので今後の拡張性を考慮した大きな作り変えの対応が可能だ。
良い単体テストとは?
単体テストはただやるだけでは効果は薄い。
そこで、ここでは良い単体テストを構成する4要素についてまとめていこうと思う。
- リグレッション(上記参照)に対する保護
元々ある機能が壊れていないことを保証する。この要素があることで、開発者はリグレッションを恐れずに製品コードを修正出来るようになる。 - リファクタリング(動きを変えずに、読みやすく・修正しやすくすること)への耐性
製品コードがリファクタリングされても、単体テストが壊れて失敗するようにならない。機能そのものが変更されない限りはテストコードは修正する必要はないという状態。 - 迅速なフィードバック
テストを実行即座に実行することが出来、その結果を迅速に受けることが出来る。
これにより、製品コードにバグが持ち込まれても即座に検知できるし、バグが修正されたかどうかも即座に確認できる。
自動化されたテストがこれに当たる。 - 保守のしやすさ
テストコードの修正が容易になり、常にメンテナンスされている状態が維持できるようになる。
3要素はトレードオフの関係にある
1~3はすべてを完璧に満たすことは出来ない、トレードオフの関係にあることを理解しておく必要がある。
例えば3.迅速なフィードバックを最優先すると、テストの粒度を小さくしすぎてしまい、2.リファクタリングに耐えられないテストが増えてしまう。
逆に2.リファクタリングへの耐性を意識しすぎると、カバレッジが落ちて1.リグレッションに対する保護が弱くなることもある。
これに対して、4.保守のしやすさ はほかの要素の影響を受けずに高めることが出来る。
そして、1~4のいずれかの要素が完全に欠如してしまうと、そのテストの価値はゼロになってしまう。
良い単体テストを構成する4要素のバランス
この4要素を適切に持つ単体テストが、良い単体テストということになる。
では、この4要素のバランスはどうしたらよいか?
結論として、正解は以下となる。
** 「4.保守のしやすさ」を確保したうえで、「2.リファクタリングへの耐性」を最大限にしつつ、「1.リグレッションに対する保護」と「3.迅速なフィードバック」の間でバランスをとる。**
この結論の細かい理由については、参考文献「単体テストの考え方/使い方」を参照してほしい。
単体テスト導入のメリットデメリットとは?
ここでは単体テストを導入することのメリットとデメリットをまとめていこう。
単体テストのメリット
- リファクタリングがしやすくなり、開発が持続可能になる
- 不具合を機能テスト前に発見できるようになる
機能テストでの手戻りが減る。 - テストコード自体が"動くドキュメント"となる
新メンバーはテストコードを見ることでソフトウェアの動きの把握が早くできるようになる。 - 単体テストを意識した設計が、より良い設計につながる
よりよい設計と言われる疎結合に向かっていく。
単体テストのデメリット
- テストコードを書くのに時間がかかる
製品コードを書くのと同じくらいの時間がかかる。 - テストコードをメンテナンスするのにも時間がかかる
質の悪いテストはメンテナンスコストを増大させる。
とにかくこれまで書いていなかったテストコードを書くことになるので時間がかかる。つまり短期的には開発にかかるコストが増えるということになる。
それに加えて、テストコードの質が悪いとテストコードのメンテナンスにも多くの時間が必要になってしまう。
単体テストのメリットを十分に享受するには、前述したような"よい良い単体テスト"を構築していく必要がある。
良い単体テストを持つソフトウェアは持続的に成長することが出来る
単体テストは「開発コストを下げるためのもの」ではなく、「ソフトウェアを成長させ続けるための投資」である。
単体テストがないソフトウェア開発は、長期的には崩壊してしまう。
短期的にはコストが増えるかもしれないが、長期的には単体テストはソフトウェアの成長を持続可能なものにする。
単体テストの環境が整って、初めて開発者はソフトウェアを成長させられる。
膨張し続けるソフトウェアを成長させ続けるには、単体テストが必要不可欠なのである。