Twitter Facebook github

バグを減らすためにユニットテストを考える

ユニットテスト基礎

前提として

  • 「バグがある」ことを証明できるが、「バグがない」ことは証明できない
  • バグを限りなくゼロに減らすために有限時間内でチェックを行っている
  • ステートが増える毎にテストケースとテスト時間は指数関数的に増加する
  • ウェブサービスでブラウザを介して行うものは完全なブラックボックステストであるし、仮に誤った出力となるケースを発見しても、そのリクエスト内のどの段階(メソッド)でバグが起きているのか特定することはできない

基礎知識

テストは最低限の品質保証です。

テストを行っていなかったプロジェクトにテストを導入する場合、「この関数の結果は最低限、保証されていなければならない」とする関数を最優先のテスト対象とします。
ただし、テストを実施するためには、テストを行いやすい設計が必要不可欠であるため、既存のコードのままテストを実施しようとは考えない方が良いでしょう。

また、ユニットテストは、リグレッションテスト(デグレードテスト)を行うための最適な方法です。リグレッション(デグレード)というのは、新たに機能を追加したり、既存のコードのバグを修正した際に、新たなバグを生みだしてしまうことです。ユニットテストを正しく行っていれば、常に要求を満たすテストケースによって過去の修正内容も検証され続けることになるためです。

またテストコードを書く習慣のない人が書いたコードは総じてテストしにくいコードを書く傾向にあり、テスト導入を決めたプロジェクトでテストコードに関する知識の無い人にコードを書かせ続けるのはとても非効率です。…それでも、テストコードを書く人が指摘しないといつまでも書けるようにはなりません。

テストコードは誰でも書けるのか

正しいテストコードを書くにはプログラミングの知識だけでなく、テスト工程におけるテストスキルと経験も必須です。

機械的に行うユニットテストなどのソフトウェアテストは、ブラウザを介した出力を人間が目視で行うブラックボックステストと根本的に異なります。たとえば今回取り上げているのはユニットテストですが、その具体的なテストケースには同値分析や境界線分析など多くの手法があり、また適切なテストケースを書くことができないのであれば、テストコードを書かないも同然です。

なにより、実施しようとしているテストの基礎知識がなければ、どの程度テストコード、テストケース、カバレッジを維持すれば十分という判断をすることができないし、そもそもプログラミングの知識がなければテストコードを書くことも出来なければ、テスト対象のコードを読んで適切なテストケースを作ることも不可能です。

ゆえに、テストコードを書くにはテスト対象のコード(つまりビジネスロジック)を書く以上のスキルと経験が必要と言えます。

テストコードを書かないのは悪ではない

ユニットテストはソフトウェアテスト手法の一つですが、これは通常の開発手法(例えばウォーターフォールやアジャイル、スクラム等々)と同様に、適用して効果的なプロジェクトもあれば、むしろ適用した結果マイナスに作用してしまうプロジェクトもあり、すべてのプロジェクトに導入を勧められるものではないことを理解してください。

そのため、事前にユニットテストを導入するのが本当に効果的なのかどうかを検討・判断する必要があります。

ユニットテストが効果的なプロジェクトの条件を次に挙げます。

頻繁にリリースが行われる

例えばリリースが年に数回のように、頻繁にリリースをしない場合、手動テストのコストが相対的に小さくなり、ユニットテストを導入するコストの方が大きくなります。逆にアジャイルで毎日デプロイを繰り返すようなプロジェクトの場合、手動テストだけでは太刀打ちできなくなることでしょう。

長い期間保守される

リリース頻度と同じ考え方ですが、短時間で保守されなくなるプロジェクトでは、トータル的に自動テストが実行される回数が少なくなるため導入コストの方が大きくなります。例えばサービス案を練って試作してみる時のプロトタイプなどに導入するのは不向きということになります。

多くの人数が開発に関わる

例えば1人で開発を継続できる規模のプロジェクトでは、当人がすべてのコード・仕様を把握しているケースが多く、テストコードを導入したとしても成果物の品質に影響を与えないことが多いです。逆に数十人規模の開発チームの場合、一人がすべてのコードを把握するのは不可能でしょうし、初めて触れるコンポーネントの改修を安心して行うことができるようになります。

ただし、頻繁に参加しているメンバーが変動するプロジェクトの場合、テストコードのメンテナンスコストが高まり、テスト自体が破綻することがあるので注意が必要です。

また、ある程度コードの規模が大きくなってきたプロジェクトにおいては、トータルで見たとき「自動テストを導入した後の開発速度」の方が導入前と比べて早いことも多いのですが、その段階以前の小規模なプロジェクトでは、自動テストを導入しない方が開発速度は速くなります。実際のビジネスの世界では品質よりスピードを重視することも多々あるので、開発ステージに応じた選択が必要になります。

「テスト駆動開発」のススメ

テスト駆動開発とは

一般的な開発工程では、機能実装者=その機能のテストコード実装者になります。

ユニットテストは学校のテストに例えると、

  • テストコード=問題(先生が出題する)
  • ビジネスロジック(機能)=解答(生徒が解く)

なので、

  • 「先に答え(機能)を書いてから、問題を出題(テストコードを実装)する」

より、

  • 「先に問題(テストコード)を書いてから、その問題に答える(機能を実装する)」

方が考え方としてはより適切であり、成果物の品質向上を期待できます。

このように、先にテストコードを書いてからビジネスロジックを実装する開発手法をテスト駆動開発(テストファースト)と言います。

テスト駆動開発には、テスト対象のコードを書く前の時点でその全体像を頭の中で設計する必要があり、プログラム初学者が習得するのは困難です。

テスト駆動開発 は開発が遅くなる?

テスト駆動開発を実施すると生産性が下がると主張する人も多いのですが、実際は正しくテストが行えていればトータルで見た生産性は上がります。

何故かというと、実際にコードを書くエンジニアでないと分からない感覚かもしれませんが、エンジニアが「実際にコードを書く時間」より、「処理を頭の中で考えて設計する時間」と、「書いた後の動作チェックの時間」の方が長くなりがちです(そうでないケースを私は知りません)。

特に新人の頃は設計も未熟であり、十分に頭の中でシュミレーションできず、いきなり手を動かしてコードを書く作業に入るために無駄なコードが増えがちです。
しかしその繰り返しによって知見が十分に溜まることにより、手を動かす前に今から書こうとするプログラムの全体像を自然とイメージできるようになります。
経験を積み重ねた熟練者はコードの実行結果を頭の中でシュミレーションできるので、無駄なプログラミングを初めからしなくなります。

従って、テスト駆動開発の導入によりテストコードを先に書くことによって、開発工程で一番時間のかかる「脳内設計工程」と「動作チェック工程」が一体化し生産性が向上します。

結論

何が言いたいのか分からなくなってきました。ここまで書くのに疲れました。また考えておきます。

所感

ここまで語れるほどユニットテストの経験が長いわけではない…はずが、いつの間にか書けるくらいの知識量になっていました。初学者だった頃は「テスト難しそう…書くと遅くなるし要らないや」なんてよく思っていました。懐かしいです。

コメントを残す

メールアドレスが公開されることはありません。