Java単体テストのベストプラクティス:テストの自動化を最大限に活用する方法

unit testing best practices

単体テストは誰もが知っているプラクティスですが、まだまだ改善の余地はあります。この記事では、自動化ツールを最大限に活用するためのアプローチも含め、単体テストのベストプラクティスについて説明します。また、コードカバレッジ、依存関係のモック、および全体的なテスト戦略についても説明します。

単体テスト(ユニットテスト)とは

単体テストとは、個々のユニットまたはアプリケーションのコンポーネントをテストして、各ユニットが正しく機能していることを検証するというプラクティスです。一般的に、ユニットはアプリケーションの小さなパーツであるべきです – Javaでは通常、単一のクラスです。ただし、私はここで「ユニット」を厳密に定義するつもりはありません。テストごとにテスト対象コードの範囲を決めるのは開発者です。

「単体テスト」という用語は、「統合テスト」または「エンドツーエンドテスト」と対比されることがあります。違いは、一般に単体テストの目的が個々のテスト可能なユニットの動作を検証することであるのに対し、統合テストの目的は、複数のコンポーネントの動作またはアプリケーション全体の動作の検証であることです。繰り返しになりますが、何が「ユニット」かは厳密に定義されていません。各テストの範囲はあなた次第です。

単体テストを行う理由

単体テストは、ソフトウェアの品質を保証するための定番のテクニックであり、多くの利点があります。以下に、単体テストを行うべき強力な理由をいくつか(それ以上)挙げます。

  • 単体テストは、ソフトウェアの各部分が現在正しく機能しているだけでなく、将来も継続して機能することを検証し、将来の開発のための強固な基盤を提供します。
  • 単体テストは、製造プロセスの初期段階で欠陥を検出するため、開発サイクルの後期段階で欠陥を修正するコストを削減できます。
  • 単体テストを行っているコードでは、テストをすばやく再実行して動作が変わっていないことを検証できるため、一般的に比較的安心してリファクタリングを行うことが可能です。
  • 単体テストを書くことで、開発者はコードがどれほど単体テストに適しているかを考慮するようになり、別の視点からコードを眺め、実装においてコーナーケースやエラー条件を検討することが増えます。
  • コードレビュープロセスに単体テストを組み込むと、修正されたコードまたは新しいコードがどのように機能するべきかが明らかになります。さらに、レビュー担当者はテストの質を評価できます。

残念ながら、開発者が単体テストをまったく書いていない、 十分なテストを書いていない、あるいはテストをメンテナンスしていないケースが多々見受けられます。単体テストを書くのが難しかったり、メンテナンスに時間がかかる場合がある-それは私も理解しています。締切があり、テストを書いていると締切に間に合わないような気がする、そういう場合もあります。しかし、十分な単体テストを作成していない、または適切な単体テストを作成していないというのは、危険な落とし穴です。

そこで、私がこれから提案するベストプラクティスを検討してみてください。これは、クリーンでメンテナンスが容易な自動テストを作成し、最小の時間と労力で単体テストのすべてのメリットを手にするためのガイドラインです。

単体テストのベストプラクティス

最良の結果を得るために、単体テストの構築、実行、メンテナンスに関するベストプラクティスを見てみましょう。

単体テストは信頼性がなければならない

コードが破損している場合、テストは失敗しなければなりません。また、コードが破損している場合以外にテストが失敗してはいけません。そうでなければ、テスト結果は信頼できません。

単体テストはメンテナンスが容易で読みやすくなければならない

製品コードが変更されたとき、たいていはテストも変更する必要があり、おそらくデバッグも必要になるでしょう。したがって、テストを作成した人だけでなく、他の開発者にとっても読みやすく理解しやすいテストでなければなりません。テストの構造や名前を決めるときは、常に明確さと読みやすさを心がけてください。

単体テストは単一のユースケースを検証する

優れたテストは1つのことだけを検証します。つまり、通常は1つのユースケースを検証します。このベストプラクティスに従うと、テストがより簡潔で理解しやすくなり、メンテナンスやデバッグも容易です。複数のことを検証するテストは複雑になりやすく、メンテナンスに時間がかかります。これを避けましょう。

もう1つのベストプラクティスは、アサーションの数を最小限にすることです。テストごとにアサーションを1つだけにするよう推奨する人もいます(これは少し制限が厳しすぎるかもしれません)。その意図は、テストしているユースケースに必要な検証だけに焦点を絞ることです。

単体テストを分離する

テストは、互いに影響を与えることなく、任意のマシンで任意の順序で実行可能であるべきです。可能なかぎり、環境要因やグローバルな/外部の状態に依存するべきではありません。このような依存関係を持つテストは実行が難しく、不安定であることが多く、デバッグや修正が難しくなり、結局、テストによって節約されたよりも多くの時間がかかることになります(上記の信頼性の項を参照)。

数年前、Martin Fowlerは「孤独な(solitary)」コードと「社交的な(sociable)」コードについて書き 、アプリケーションコードの依存関係の度合いと、それに応じてテストをどのように設計する必要があるかを説明しました。記事によれば、「孤独な」コードとは他のユニットに依存しない(自己完結型)コードであり、「社交的な」コードとは他のコンポーネントと対話するコードです。アプリケーションコードが孤独な場合は、テストは単純です。しかし、社交的なコードをテストする場合は、「孤独な」テストを作成するか、「社交的な」テストを作成するかの選択肢があります。 「社交的なテスト」は実際の依存関係を使用して振る舞いを検証するいっぽう、「孤独なテスト」はテスト対象のコードを依存関係から分離します。モックを使用すると、テスト対象コードを隔離し、「社交的な」コードに対して「孤独な」テストを作成できます。その方法を以下で見ていきます。

図1:社交的なテストと孤独なテスト 出典:Martin Fowler、2014年、 “UnitTest”
一般的に、依存関係の代わりにモックを使用すると、社交的なコードに対して「孤独なテスト」を生成できるため、テスターにとっては物事が楽になります。複雑なコードに対して社交的なテストを行うのは、多くの設定を必要とし、分離され繰り返し可能であるべきという原則に違反する可能性があります。いっぽう、モックはテスト内で作成と設定が行われるので自己完結しており、依存関係の振る舞いをより自由に制御できます。また、より多くのコードパスをテストすることができます。たとえば、モックからカスタム値を返したり、例外をスローしたりすることで、境界条件やエラー条件をカバーできます。

単体テストを自動化する

必ず自動化されたプロセスでテストを実行してください。日ごと、または1時間ごとに実行するのでも、継続的インテグレーションまたはデリバリープロセスの一部として実行するのでもかまいません。チームの全員がレポートにアクセスしてレビューできるようにする必要があります。コードのカバレッジ、修正されたコードのカバレッジ、実行されているテストの数、パフォーマンスなど、どの指標を気にするべきかについて、チームで話し合ってください。

これらの数字から多くを学ぶことができます。数値に大きな変化があった場合、ただちに対処できるレグレッションの存在を示唆しているケースがよくあります。

単体テストと統合テストをうまく組み合わせて使う

Michael Cohnの著書、 Succeeding with Agile: Software Development Using Scrumは、テストピラミッドモデルを使用してこれを説明しています(下の図解を参照)。これは、テストリソースの理想的な配分を記述するために広く使用されているモデルです。考え方としては、一般的に、ピラミッドの上に行くほどテストは構築が複雑になり、壊れやすくなり、実行が遅くなり、デバッグが遅くなるというものです。レベルが低いほどテストは分離され、自己完結しており、速く、構築とデバッグが容易です。したがって、自動化された単体テストがテストの大部分を占めるべきです。

testing pyramid

単体テストは、あらゆる詳細、コーナーケース、境界条件などを検証する必要があります。コンポーネント、統合、UI、および機能テストは、APIまたはアプリケーション全体の動作を検証するために、より控えめに使用するべきです。手動テストはピラミッド構造全体の中で最低限の割合であるべきですが、それでもリリースの受け入れや探索的テストの手段として有効です。このモデルは組織に高レベルの自動化とテストカバレッジをもたらすため、テストの規模が大きくなっても、テストの構築、実行、メンテナンスに関連するコストを最小限に保つことができます。

単体テストを組織化されたテストプラクティスの一部として実行する

あらゆるレベルでテストの成功を推進し、単体テストプロセスをスケーラブルかつ持続可能にするには、さらにいくつかのプラクティスが必要になります。第一に、アプリケーションコードを作成するのと同時に単体テストを作成する必要があります。アプリケーションコードの前にテストを作成する組織もあります( テスト駆動型またはビヘイビア駆動型プログラミング)。重要なことは、テストとアプリケーションコードを密接に関連させることです。テストとアプリケーションコードは、コードレビュープロセスでも一緒にレビューする必要があります。レビューは作成中のコードを理解するのに役立つ(期待される動作を明確にする)だけでなく、テストの改善にも役立ちます。

コードと一緒にテストを書くというプラクティスは、新しい機能の実装やあらかじめ計画された変更を行う場合だけでなく、バグ修正でも重要です。バグを修正するたびに、そのバグが修正されたことを検証するテストを作成するべきです。これにより、バグがその後も再発していないことを保証できます。

不合格のテストに関しては、ゼロトレランスポリシー(小さな違反を見逃さない方式)を採用してください。テスト結果を無視するくらいなら、そもそもなぜテストするのでしょうか?テストの失敗は実際の問題を示しているはずです。したがって、QAの時間を無駄にしたり、さらに悪いことには、問題がリリースされた製品に取り込まれる前に、ただちに対処します。

障害への対処に時間がかかるほど、障害の最終的な金銭的、時間的コストが増えます。そのため、リファクタリング中にテストを実行し、コードをコミットする直前にテストを実行し、テストが合格するまでタスクを「完了」と見なさないようにします。

最後に、 テストをメンテナンスします前述したように、アプリケーションが変更されたときにテストを最新の状態にしていないと、テストの価値は失われます。特にテストが失敗する場合、失敗するたびに調査する必要があるため、時間と費用がかかります。コードが変更されたら、必要に応じてテストをリファクタリングします。

ご覧のとおり、単体テストへの投資から最大の金銭的、時間的収益を得るには、ベストプラクティスを実践するために多少の投資が必要です。しかし最終的には、初期投資に見合う見返りがあります。

コードカバレッジについて

一般に、コードカバレッジは、自動テストの実行中にどれだけのコードが実行されたかを示す尺度です。一連のテストを実行してコードカバレッジデータを調べることで、アプリケーションがどれほどテストされたかについて、おおよその感覚をつかむことができます。

コードカバレッジには多くの種類があります – 最も一般的なものは行カバレッジとブランチカバレッジです。ほとんどのツールは行カバレッジだけを取得します。このカバレッジは、特定の行がカバーされたかどうかを示すだけです。ブランチ カバレッジでは、コード内の各パスがカバーされているかどうかがわかるので、よりきめ細かく判断できます。

コードカバレッジは重要な指標ですが、カバレッジを増やすことは、あくまで目的を達成するための手段であることを忘れないでください。テストされていない領域を見つけるのには最適ですが、それだけに集中するべきではありません。100%のカバレッジを達成するためにあまりにも多くの努力を費やさないように注意してください – それは不可能または非現実的なことかもしれません。本当に重要なのはテストの品質です。とはいえ、プロジェクトに対して少なくとも60%のカバレッジを達成するのは出発点として悪くありませんし、80%以上のカバレッジを目指すのもよい目標でしょう。ゴールはあなた次第です。

コードカバレッジを測定するだけでなく、変更されたコードがどれだけテストでカバーされているかを自動化ツールで追跡できるなら、それも重要です。

ここで、Parasoftのレポートおよび分析ハブのコードカバレッジレポートの例を見てみましょう。 Parasoft Jtestを使用して単体テストを行っている場合、このレポートを参照できます。

Two Big Traps of Code Coverage Image 2

また、新しいテストを作成するときには、行カバレッジだけに気をとられないよう注意します。コードを1行追加すると、複数のコードパスが発生する可能性があります。テストでこれらのコードパスを検証するようにしてください。行カバレッジは簡易的な指標として便利ですが、追求するべきものはそれだけではありません。

カバレッジを向上させる最も明白な方法は、テスト対象メソッドのより多くのコードパス、より多くのユースケースに対してテストを追加することです。パラメータ化されたテストの使用は、カバレッジを向上させる強力な手段です。Junit4には、ビルトインのJunit4 Parameterized機能とJunitParamsのようなサードパーティ製ライブラリがありました。Junit5にはビルトインのパラメーター化機能があります。

最後に、まだテストカバレッジを継続的に計測していない場合、計測を開始することを強くお勧めします。Parasoft Jtestのような、役に立つツールが多数あります。現在のカバレッジを測定することから始めて、到達すべき目標を設定します。まず、カバーされていない重要な部分に対処した後、他の部分でも対処を進めます。

まとめ

単体テストはソフトウェアの品質を保証するための定評ある手法ですが、依然として開発者にとって負担と考えられており、多くのチームが苦労しています。テストツールや自動テストツールを最大限に活用するためには、テストは信頼性が高く、メンテナンスが容易で、読みやすく、自己完結型で、単一のユースケースを検証するものでなければなりません。自動化は、実行可能かつスケーラブルな単体テストを実現するための鍵です。

さらに、ソフトウェア開発チームは、アプリケーションコードと並行してテストを作成しレビューする、テストをメンテナンスする、失敗したテストを追跡してただちに修正するなど、優れたテスト手法を実践する必要があります。これらの単体テストのベストプラクティスを採用すると、単体テストの結果を迅速に改善できます。

(この記事は、開発元Parasoft社 Blog 「Java Unit Testing Best Practices: How to Get the Most out of Your Test Automation」2019年5月14日の翻訳記事です。)

 

Parasoft Jtestについて

Java対応静的解析・単体テストツール Parasoft Jtest

Jtestは、テスト工数の大幅削減とセキュアで高品質なJavaシステムの開発を強力にサポートするJava対応テストツールです。1,000個以上のコーディング規約をもとにソースコードを静的に解析し、プログラムの問題点や処理フローに潜む検出困難なエラーを検出します。さらに、JUnitを用いた単体テストについて、作成、実行、テストカバレッジ分析、テスト資産の管理といった単体テストに係る作業をサポートし、単体テストの効率化を促進します。

Top