Mockでユニットテストを簡単にしよう!

はじめに

こんにちは。NIKKOエンジニアのS.TKです。

皆さん、テストはしていますか?最近の開発手法であれば、ほぼ確実にテストが考慮されているので嫌でもしていますよね。ただ、テストって実は結構難しかったりします。特にテストコードを書くとなると、プロダクトコードの設計によってはかなり苦労させられます。

そこで、今回はユニットテスト(単体テスト)に焦点を当て、テストコードを楽に書くためにMock(モック)を利用する方法をご紹介します。私はGMO MARS DMPの開発・運用を担当していますが、今回ご紹介する内容は普段の業務で実践している内容になります。
言語はJavaで、テストフレームワークはJUnitを使うことにします。

ユニットテストを書こう

まず最初に、ユニットテストを書くことの意義について再確認してみたいと思います。今更感がすごいですが、ちょっとだけお付き合いください。

一番期待されるのは、やはり品質の向上でしょうか。誤りを早い段階で発見できますし、既存機能を変更する際に誤りを防止することができます。また、プロダクトコードをリファクタリングする際には、ユニットテストが誤り混入の防波堤の役割を果たしますので、機能面だけでなくコード面の品質も向上させることができます。

他にも、テストコードの書き方を工夫するとプロダクトコードの使い方を明示することができます。つまり、テスト対象クラスの使い方サンプルとしてテストコードを利用することができます。テストコードは実際に動作可能なので、ドキュメントよりも信頼性が高いはずです(テストコードが「古い」場合はテストがfailする可能性が高いため)。

さらに進めて考えると、設計の品質向上にも貢献します。ユニットテストの容易さで設計のマズさがある程度わかるためです。これについては、本稿で実感頂けるかと思います。

ユニットテストでよく発生する問題

さて、ユニットテストの大切さを再確認したところで、実際にテストコードを書いてみると……これが案外難しいものです。テスト対象クラスに外部リソースにアクセスする処理や実行環境に依存する処理があり、自分のローカル環境でテストが動かせない……というのはありがちです。

たとえば、下記のようなidからユーザ名を取得するような簡単なクラスで考えてみます。ユーザの情報はDBに格納されているため、DBに接続してデータを取得する処理が必要になります。

このクラスのユニットテストを実行することを考えると、自分のローカル環境にDBをつくるかDB接続の設定を行う必要があります。また、実際にユニットテストを実行させると、DBとの接続やテストデータ投入でかなりの時間がかかってしまいます。

Mockを使おう

このような場合、Mockを使うと上手く解決ができます。

Mockとは、簡単に言うとクラスの動作をシミュレートするためのオブジェクトです。テスト対象クラスが呼び出している(=依存している)クラスをMockで差し替え、Mockの動作内容を定義することで、望むテスト条件を容易に作ることができます。

Mockを扱うライブラリは各言語に色々存在していますが、Java言語で有名所ですとMockitoというライブラリがあります。Mockitoは1系と2系がありますが、今回は1系を扱います。

実際、Mockitoはどんな感じで使うのかというと、下記のようになります。

プロダクトコードがこちら。

テストコードがこちら。

テストコードの8行目がMockオブジェクトを作成している箇所で、11行目でそのMockの動作を規定しています。この例ですと、「isSomething()メソッドが引数100で呼ばれた時にtrueを返す」ことを定めています。そして、このテストケースではisSomething()がtrueを返してきた場合にテスト対象メソッドが正しく-1を返していることをテストしています。

このように、Mockを使うとテスト対象クラスが依存しているクラスの動作をシミュレートし、テストケースの事前条件を容易に整えることが可能となります。

Mockを実際に使ってみる

では、前述のSomeServiceのテストコードをMockを使って書いてみます。

早速、依存しているクラスをMockに差し替えてやりましょう!……依存クラスがないですね。これではMockに差し替えられません。

このようなケースは特にレガシーコードではよくあることです。以下で順を追ってプロダクトコードを修正し、Mockで差し替えられる設計にしましょう。

責務外の処理は別クラスに移譲しよう

まずDBにアクセスしている部分はSomeServiceの責務とは外れているので、別クラスに移譲してしまいましょう。たとえば、UserMapperクラスとでもしてしまいましょう。

実際に修正すると下記のようになります。

これで面倒なDBアクセス部分は別クラスになりましたので、簡単にテストコードが書け……ませんね。これだとUserMapperをインスタンス化した際に結局DBアクセスが発生してしまいますし、Mockで差し替えることもできません。

依存するオブジェクトは外部から注入できるようにしよう

では次に、UserMapperオブジェクトをインスタンス化する部分をクラス外に持って行ってしまいましょう。つまり、SomeServiceを使う側がUserMapperをインスタンス化し、それをSomeServiceに「注入」してあげることにします。注入方法はコンストラクタで良いでしょう。

やっとできました。これでDBアクセス部分をMockで差し替えることができます。

Mockでユニットテストを簡単に書こう

修正後のSomeServiceで、Mockを使って実際にテストコードを書いてみます。この場合はUserMapperが依存しているクラスなので、このクラスのMockを注入してあげればOKですね。具体的なテストコードは下記のようになります。

DBアクセスを担当するUserMapperクラスをMockにしているため、DB接続ができない環境でも問題なく動作します。これで自分のローカル環境でもガンガンテストを動かせますね!

このように、Mockを使ってあげると、余計な処理を省いて本当にテストしたい内容だけをテストコードとして書くことができます。

ユニットテストと設計の関係

前述のような依存するインスタンスを外部から注入する設計は、一般にDI(Dependency Injection)として知られています。DIコンテナも多数存在しており、有名どころではSpringなどがあげられます。
DIできるように設計しておくと、ユニットテストでMockを容易に注入できるため、テストコードがとても書きやすくなります。逆に、テストコードが書きにくいと感じたら、プロダクトコードの設計がマズイ可能性が高いです。ユニットテストによって設計の品質が向上するというのはまさにこの点を指しています。

おわりに

本稿では、ユニットテストを簡単にするためにMockを使う方法を紹介してきました。途中から設計の話も混ざってきましたが、これはユニットテストがある意味では設計作業でもあるためです。ユニットテストの容易さとクラスの使い勝手の良さは大抵の場合イコールになるものです。
この例ではJava言語を使っていますが、大抵の言語でこの考え方は通用します。皆さんも、プロダクトコードを書く時に「このコードのテストはどうやって書けば良いだろうか」という点を意識してみるのは如何でしょうか。