はじめに
Javaの開発では、多くのプロジェクトでオープンソースのテスティングフレームワーク「JUnit」が使われています。テスティングフレームワークを使った単体テストのメリットは、テストコードを一度作成することで同じ条件のテストを繰り返し実行でき、結果の確認を自動で行えることです。
「品質をもっと上げたい」「開発工数を減らしたい」といった期待を持ち、多くの開発者は単体テストや単体テストの自動化に取り組みます。しかし実際に導入し、運用してみると、「予想以上に工数がかかっている、思ったほど効果が出ない」または「運用しているが非常に苦労している、ちっとも楽にならない」といったことに陥りがちです。
ここでは、なぜそのようなことになるのか、特にありがちな3つの失敗例とその対策を紹介します。
- 【失敗例1】単体テストを導入したら工数が増大した
- 【失敗例2】単体テストを実施していたが、メンテナンスコストが増大し、やらなくなった
- 【失敗例3】ソースコードカバレッジの罠にハマる
本稿ではそれぞれの失敗例に対する一般的な対策を紹介しますが、それだけでは劇的な改善を望むことはできません。
そこで、失敗例の対策を加速させるために、商用ツールであるParasoft社製「Jtest」の「単体テストアシスタント(Unit Test Assistant)」を使う方法も併せて紹介します。
Parasoft社の調査によると、単体テストアシスタントを使用している顧客からのデータでは、単体テストの労力が50%も削減されているとのことです。また、私たちの独自の検証では、単体テストに慣れていない新人開発者において、実際に工数を約33%減らすことができました。これは衝撃的な数字です。
どうすれば単体テストで期待通りの効果が出せるのか、失敗例と対策から見ていきましょう。
【失敗例1】単体テストを導入したら工数が増大した
単体テストを実施するためにはコストがかかる、ということはよく知られていますが、予想以上に工数がかかり過ぎて社内に普及しなかったり、手間がかかり過ぎて開発者の負担が大きくなってしまったりすることはないでしょうか? また、単体テストをすでに運用している場合でも負担を減らしたい、といった話もよく聞きます。
では、なぜ工数が増大するのでしょうか?
【原因】1.実装コストがかかる
単体テストはプロダクションコードと同じ、またはそれ以上のテストコードの実装が必要であり、多くの時間がかかります。また、テストもプログラムなので、意図した通りにテストが動作するかを確認するためデバッグも行う必要があり、工数がかさんでいきます。
【原因】2.学習コストがかかる
新人や経験の浅い開発者がプロジェクトに参画した場合、JUnitの実装やテストの方法を学習する必要があります。また、Javaの開発では当たり前のようにアプリケーション開発フレームワーク(Springなど)を使用しますが、そのテストはフレームワーク独自の手続きが必要な場合もあるため、学習が必要です。
それぞれへの対策としては、以下が挙げられます。
【対策】1.実装コストがかかる
一般的な対策
テストコードの記述にはどうしても時間がかかります。そこで、プロダクションコードをテストしやすいコードにすることで実装にかかる時間を削減します。単体テストを考慮していないコードは複雑になりがちで、テストの作成が難しく、単体テストの実装に時間がかかります。テストを見越した設計や実装を行うことで、シンプルな設計、実装になり、テストの作成にかかる時間を削減します。
さらにJtestによる対策「テンプレート機能を使って実装コストを削減」
Jtestのテンプレート自動生成機能を利用することで、開発者の手作業を減らし、デバッグも効率良くできるようになります。Jtestでは以下のようにテストを作成します。
(1)テストケースの作成
クリックひとつでテストケースのテンプレートを作成します。テストケースのスケルトンだけでなく、値の設定が必要なパラメータ定義も作成します。さらに、CSVからテストデータを流し込むパラメータライズのテンプレートも作成可能です。
(2)アサートのテンプレート作成
テスト実行後の結果を期待値に設定することができます。テスト実行後、期待した結果が得られたことを確認し、クリックひとつでアサートを作成します。
(3)テストのデバッグ
期待通りの結果にならなかった場合、ステップ実行やログ出力を追加して動作の確認を行います。Jtestではテストの実行を監視し、実行時の処理経路(スタックトレース)やメソッドの戻り値、引数の値を記録します。そのため、プロダクションコードのどのコードが実行され、どのように値が変化し、処理されたのかを瞬時に確認し、デバッグ作業の手間を削減します。
【対策】2.学習コストがかかる
一般的な対策
経験の少ない開発者に対しては、テストしやすいクラスから経験を積んでいきます。例えば、依存関係の少ないユーティリティクラスや、処理が複雑ではないPOJOなどを選んでテストの実装方法や進め方に慣れてもらいます。学習コストはかかりますが、簡単なところから徐々に範囲を広げていくことで、失敗を防ぎます。
さらにJtestによる対策「テンプレートを自動生成することで学習コストを削減」
Jtestではテストケース、アサートだけではなく、モックやフレームワークなどさまざまなテンプレートを自動で作成することで、単体テストの学習コストを削減します。
(1)モックテンプレートの作成
モックの複雑な呼び出し手順を覚える、振る舞いを理解するには経験が必要です。Jtestではモックのテンプレートの作成や、モックを監視して依存関係を自動で検出することで、間違えやすい手作業のコーディングを削減します。
(2)Springテストケースのテンプレートの作成
Springは独自のテストフレームワークを提供しており、効率良くテストが実施できますが、その使い方を学習する必要があります。Jtestを使うと、Spring特有の手続きが記述されたテンプレートが作成されるため、新たに学習する時間を削減することができます。
Jtestの無料体験版を入手!
Jtestの無料体験版を配布しております。以下のリンクからダウンロード可能です。
【失敗例2】単体テストを実施していたが、メンテナンスコストが増大し、やらなくなった
プロダクションコードの仕様変更や修正が発生した場合、テストケースもそれに合わせて修正していく必要があります。しかし、そのコストが大きくなり過ぎてメンテナンスできなくなり、テストケースが使われなくなっていった経験はないでしょうか? 長期にわたるプロジェクトや頻繁に開発者が変わるプロジェクトなどでよくみられます。
なぜメンテナンスコストがかかるのでしょうか?
【原因】1.テストコードが複雑過ぎて修正に時間がかかる
巨大なテストメソッドを作成したり、テストパターンを増やすためにコピペが多くなったりすると修正の影響範囲が大きくなるため、テストコードを理解することが困難になっていきます。
【原因】2.いつの間にか失敗するテストケースが増えて修正が大変
テストの実行に時間がかかるため、頻繁にテストを行わない、何かのイベントの区切りにしか行わない、軽微な修正なのでテストケースを実行しない、といったことが積み重なると、いつの間にかテストケースが失敗するようになります。そして、原因究明と修正に多くの時間がかかってしまいます。
対策としては、以下が挙げられます。
【対策】1.テストコードが複雑過ぎて修正に時間がかかる
一般的な対策
テストコードもプロダクションコードと同様に、保守の間は利用され続けるものです。メンテナンス性を考えて、複雑になり過ぎないように重複をまとめたり、分割したりするなど、しっかり設計をして実装するべきです。テストパターンを増やすにはテストコードのコピペではなく、JUnitのパラメータライズを使うことで、1つのテストケースで複数のパターンをテストできるようになります。
さらにJtestによる対策「シンプルなテストケース作成を標準化」
JUnitのパラメータライズは【失敗例1】でも紹介したテンプレート機能で簡単に作成することができます。また、さまざまなテンプレート作成機能を使うことで、テストケースの実装を標準化することができます。経験の少ない開発者でも一定のレベルで定型化されたテストケースを作成することができるため、複雑化を防ぎます。
【対策】2.いつの間にか失敗するテストケースが増えて修正が大変
一般的な対策
いつでもテストを自動で実行する仕組みを作ります。ビルドや単体テストを自動的に実行するCI環境を構築することで、コミットのタイミングでテストを実行するなど、開発者が意識せずに常にテストが実行される環境を作れるため、テストの失敗を早めに検知し、修正することができます。
さらにJtestによる対策「修正の手間を省き、いつでも実行可能な環境を構築」
アサートが失敗したテストケースにおいては、実行後期待通りの結果が得られていることが確認できれば、簡単に期待値を更新することができます。
テストの実行も、Jtestを使えば変更箇所だけテストを実行することができるため、テスト実行に時間がかかって待ちが発生することもなくなり、テンポ良くテストを行うことができます。
また、JtestはCIツールとの連携も可能です。特にJenkinsであれば、Parasoft社の提供しているプラグインを利用することもできます。また、テクマトリックスでは「Jenkins Package Platform for Java」という、静的解析や単体テストの実行環境を整備するためのソリューションを提供しています。
Jtestの無料体験版を入手!
Jtestの無料体験版を配布しております。以下のリンクからダウンロード可能です。
【失敗例3】ソースコードカバレッジの罠にハマる
単体テストで計測されるソースコードカバレッジは、テストの進捗や、どの程度テストが実施されたのかを表す指針として有効です。しかし、カバレッジにこだわり過ぎることによって、工数が増大し、バグが多く残ってしまい、せっかく工数を割いて単体テストをやっているのにメリットが感じられないといった経験はありませんか?
なぜソースコードカバレッジの罠にハマってしまうのでしょうか?
【原因】1.カバレッジを100%にするために工数がかかった
単体テストのゴールを「カバレッジ100%」や「すべてのメソッドをテスト」と設定することがあります。しかし、それにこだわり過ぎると、単体テストの実装が難しい箇所のカバレッジを上げるために、テストコードの実装に多くの時間を割き、予想以上に工数がかかってしまいます。
【原因】2.カバレッジ100%を達成したが、後工程でバグが多発した
カバレッジが単体テストのゴールになっていると、ついついテストはカバレッジを上げれば良い、と思ってしまいがちです。そのため、テストケースにもカバレッジを上げるためだけのものが実装され、本来行われるべきテストが行われず、バグを見落とすことがあります。これではせっかく単体テストを実施し、カバレッジも計測しているのに品質が上がりません。
対策としては、以下が挙げられます。
【対策】1.カバレッジを100%にするために工数がかかった
一般的な対策
限られた時間で効果的なテストを行うためには、カバレッジにこだわり過ぎず、テスト対象を絞り込むことも有効な方法です。
では、どうやってテスト対象を絞り込めばいいのでしょうか? 例えば以下の基準でテスト対象を絞り込みます。
テストが必須
- ビジネスロジック(MVCのModel部分)
- 新規コード、追加、修正コード(保守開発の場合)
テストを検討
-
以下のいずれかに当てはまるコード
- 複雑なコード、大きなコード
- 多くのプログラムから参照されるコード
- 変更が頻繁にあるコード
テストから除外
-
実際に操作してテストしたほうが効率的にテストできる部分
- 画面表示部やGUIと密接に関わっている箇所など
例えば、全体の80%をカバーできているプロジェクトで、残り20%の単体テストがやりづらい部分に時間をかけるのであれば、アプリケーションを動かして行うテストでカバーする、手動でテストを行う、というのもひとつの方法です。
さらにJtestによる対策「テスト全体でカバレッジを計測」
単体テストの対象から除外した箇所に対しては実際にアプリケーションを動作させてテストを行います。Jtestでは、アプリケーションを動作させた場合でもカバレッジを取得することができるため、テストの抜け漏れがないか確認できます。さらに単体テストのカバレッジと合算することもできるため、異なるテストでもプロジェクト全体のカバー率を見える化することができます。
【対策】2.カバレッジ100%を達成したのに、後工程でバグが多発した
一般的な対策
はじめに、バグには種類があり、大きく分けて2つのバグがあることを知る必要があります。
- NullPointerExceptionなどのランタイムエラー
- 目的通りにプログラムが動作しない仕様に関するバグ
単体テストは2番目の「目的通りにプログラムが動作しない仕様に関するバグ」を早期に発見し、対処することが主な目的です。そのため、正しくテストを行うためには正しい順序、方法でテストを行う必要があります。
例えば以下の順序・方法で行います。
テスト手法 | テストの目的 |
---|---|
ブラックボックステスト | 仕様通りに動作するかを確認するテスト |
ホワイトボックステスト | 実装したコードを網羅的にテストすることで、すべてのコードが動作することを確認し、堅牢性を高めるためのテスト |
目的に合わせたテストを行うことで、単体テストの効果を発揮できるようにします。
さらにJtestによる対策「カバレッジを上手に使う」
単体テストは「目的通りにプログラムが動作しない仕様に関するバグ」を早期に発見することが目的ですが、期待通りのテストが行われているか、テストに漏れが発生していないか、確認を行うべきです。
Jtestのカバレッジアシスト機能を使うと、Jtestが実行されていない処理を見つけて警告をレポートしてくれるので、テスト漏れを防ぐことができます。
まとめ
単体テストには明らかなメリットがあることを、多くの開発者やプロジェクトは認識しています。しかし、せっかく導入した単体テストがうまく運用に乗らなかった、今も苦労している、といった声があることも確かです。単体テストのメリットを享受するためには失敗例を知り、対策を立てることが重要です。
さらに、失敗を回避するために商用ツールのJtestを使うことも選択肢のひとつです。冒頭にも書きましたが、Parasoft社の調査によると、Jtestの単体テストアシスタントを使用している顧客からのデータでは単体テストの労力が50%も削減されています。
同社の最近の調査では、開発者の作業時間の約40%が単体テストに使われているそうです。10日間の実稼働日の開発スプリントで考えると、4日間はテストに使われることになります。つまり、この開発スプリントの例で見ると、4日かかっていたテストが2日で終わることになるため、工数削減や早期リリースを実現できます。
Jtestは工数だけでなく、テンプレート作成や処理フローの監視などさまざまなアシスタント機能によって、単体テストを成功に導きます。単体テストで苦労している、うまくいかなかった経験がある、という方はぜひJtestをお試しください。
Jtestの無料体験版を入手!
Jtestの無料体験版を配布しております。以下のリンクからダウンロード可能です。