アプリケーションを作成した際、動作確認のためにテストが必要になります。今回は自分が作成したJavaアプリケーションとそれに対する実際のテストコードを見ながら、コツや注意点をシェアできればと思います。
単体テストとは
アプリケーション本体のプログラムを作成した後、普通はその動作を確認したいと思うはずです。そのためにアプリケーションを実際の環境(本番環境)で実行して確認する方法があります。しかしいきなり本番環境で実行するにはリスクが大きかったり、アプリケーションを本番環境に持っていくのにそもそも手間がかかるケースもあります。そのため作成したアプリケーションの動作を確認するためのプログラムを書くことが当たり前になっており、そのプログラムをテストコード、確認作業自体を単体テストと呼んだりします。
動作確認のためにプログラムを書くのは一見手間のようにも見えますが、アプリケーションを改修するたびに目視等で確認するのは骨が折れますし、バグを見落とす可能性もあります。その点テストコードを一旦書いてしまえば、自動で動作を確認できますし、改修箇所が既存コードに影響を与えていないかといったことも簡単に確認することができます。
基本的な書き方
アプリケーション本体のコードが想定通りに動いているかを確認することがテストコードの目的になります。なので基本的には本体のメソッド単位でそれに対応するテストメソッドを書いていくことになります。
例えばこのような簡単なメソッドがあったとします。
int型の2つの引数を受け取り、それらの和(足し算)を返す
このメソッドに対するテストメソッドは次のようになります。
上記のメソッドに1と2という引数を渡したときに3が返ってくることを確認する
引数の部分はただの例なので他の数値でも構いません。引数の和が正しく返ってくれば正常な動作としてテストをpassしているとみなすことができるというわけです。
様々なメソッドがあると思いますが、テストメソッドの考え方はどれも同じになります。テストメソッドの中で呼び出した本体のメソッドの結果と、本来あるべき理想的な結果(期待値)を照合して一致しているかを確認するという流れです。
ちなみにJavaのテストコードを実行するツールとしてもっとも一般的なのがJUnitになります。後述するサンプルコードでもこのJUnitを使用しています。
注意点
ではここからは実際のコード例も見ながら、テストを書く際に注意する点などをまとめていきたいと思います。ごく一般的なテストコードなどは文献やインターネット上に数多あるので、ここではポイントに絞って解説したいと思います。
assertThatを使う
単体テストで対象メソッドの実行値と理想値を比較する際によく使われるのがassert文です。昔からJavaに触れている人はassertEquals
になじみがあるのではないでしょうか。これに対してJUnit4.4から新たに追加されたのがassertThat
になります。上述のようなテストを実行したい場合、このように書くことができます。is
はMatcherと呼ばれるもので、ここでは値を比較するために使っています。
assertThat(addMethod(1, 2), is(3));
assertEquals
でも同様のテストは実現できるのですが、assertThat
では以下のようなメリットがあります。
- 可読性が高い(英語の文章のように読める)
- 比較対象や型によってメソッドを使い分けなくてよい
- アサーションメッセージが理解しやすい
テストの際は必ずassertThat
を使わなければならないというわけではないですが、最近は使われているケースが多くなっているので、まだ使ったことがない方はぜひ試してみてください。
テスト実行時の例外
通常のメソッドと同様、テストメソッドでも例外は発生する可能性があります。通常であればtry〜catchで捕捉するか呼び出し元にthrowするのが一般的ですが、例外が発生する可能性のあるテストメソッドではthrows Exception
を付けることをおすすめします。まずtry〜catchよりthrowした方がエラー内容を詳細に出力してくれるので、デバッグの際に手助けになります。またIOExceptionなどの各Exceptionクラスではなく上位のExceptionクラスをthrowすることで、複数の例外クラスをまとめられるのと、例外の種類が変わっても吸収できるといったメリットがあります。テストメソッドは特にシンプルであることを求められるケースが多いので、try〜catchよりはthrows句でまとめてシンプルに書いてみてください。
privateメソッドのテスト
当然のことながら、privateなメソッドをテストしたいというケースもあります。ただしあくまでprivateなのでテストクラスからは普通は呼び出すことはできません。こういった場合はリフレクションという方法を使うことが可能です。サンプルコードはこのようになっています。
Method method = Main.class.getDeclaredMethod("sampleMethod", String.class);
method.setAccessible(true);
String text = method.invoke(new Main(), "test");
ここではMainクラスのsampleMethodメソッドに文字列”test”を渡してString型変数textに戻り値を格納しています。ここで2行目のsetAccessible(true)
で外部からのアクセスを許可する設定をしています。強力な方法ではありますが、privateメソッドに外部からアクセスする少し強引な方法でもあるので、テスト以外の本体のコードで使用することはおすすめできません。あくまでテストコードなどの限定的な使用にとどめるように心がけましょう。

抽象クラスのテスト
抽象クラスは各クラスの共通処理をまとめたりする関係で、具象メソッドを持つことができます。その都合で抽象クラス(の中の具象メソッド)のテストが必要になることもあると思います。ただし抽象クラスであるがゆえにテストクラス内でインスタンスを生成することはできません。
そういったときにはテストクラスの中に抽象クラスを継承した内部クラスを持たせることで解決できることがあります。内部クラスというのはクラスの中にさらにその中限定のクラスを持つということです。このクラスにテスト対象の抽象クラスを継承させることで、テストしたいメソッドをテストクラス内部で使用することができるようになります。
サンプルコードがこちらになります。
public class SampleAbstractTest {
class SampleMock extends SampleAbstract {
@Override
...
}
@Test
public void sampleMethodTest() throws Exception {
SampleMock mock = new SampleMock();
assertThat(mock.sampleMethod(arg), is(expected));
}
}
サンプルコード
今回ご説明したテストコードの詳細とアプリケーション本体のコードはこちらで管理しています。興味のある方は是非こちらをご参照ください。(設計や実装などもしご意見ありましたら遠慮なくご連絡ください)
まとめ
今回は単体テストの考え方と基本的な書き方、テストコードの留意点などをまとめました。今回の話はプログラムテスト全般に関わるお話でしたが、サンプルコード自体はJavaFXで書かれたGUIアプリケーションなので、TestFXなどを使ってGUI部分のテストも今後実装できればと思っています。その際にはまたこちらのブログで記事にしたいと思うのでご期待ください。
最後までお読みいただき、ありがとうございました!