モック、スタブ、擬似オブジェクトを使った効率的なユニットテスト2

Mock Objects

モックオブジェクト

The idea behind the mock object pattern is that you pass a phony object (the mock object) into a method that is being tested. The method must accept an abstract class or interface as its parameter, and the phony object must implement that interface. The test preloads the mock object with values to be used by the method or methods that the mock is passed in to. Tests also setup expected values and conditions for mock objects. As the methods are being tested, they call mock methods. The mock object records information such as the values that are passed in and the number of times each method is called. At the end of a test a verify() method is called on the mock object. This verify() method will assert whether or not all the expected actions were performed correctly, thus reporting whether the method being tested was implemented correctly. Note that mock objects are only used for testing and thus contain no production code.

Mock objects will typically implement an interface and sometimes extend an abstract class. For example, suppose you are writing an accounting system, and you have an interface called Account. To create a mock, you would create a class called AccountMock that implements the Account interface. A mock could be an extension of a concrete class, but this is rare and generally regarded as bad design.

Author's Note: I prefer AccountMock over MockAccount, because it groups alphabetically next to Account in most IDEs and makes life simpler if you are using code-completion or if you choose to keep your test code and your production code in the same location.

Here's a simple example of an Account interface:

モックオブジェクトパターンの背景にある考え方は、テストされているメソッドに対して擬似のオブジェクト(モックオブジェクト)を渡すということだ。このメソッドは抽象クラスかインターフェースをパラメータとして受け取るようになっていて、擬似オブジェクトはそのインターフェースを実装することになる。テストでは、モックオブジェクトが渡されるメソッドによって使用される値と一緒にモックをあらかじめセットする。また予期されるモックの値や条件もセットする。モックオブジェクトは、メソッドに渡された値やがメソッドが呼ばれた回数といった情報を保持する。テストの最後にモックオブジェクトのverify()メソッドが呼ばれる。このverify()メソッドがテストされているメソッドが正しく実装されているかをアサートすることになる。モックオブジェクトはテストをするためだけに用いられ、本体のコードは含まないことに気をつけよう。

モックオブジェクトは普通インターフェースを実装するか、時としては抽象メソッドを継承することになる。例えば、会計(勘定)システムのプログラムを書いて、Account[勘定]と呼ばれるインターフェースがあったとしよう。モックを生成するのに、Accountインターフェースを実装したAccountMockと呼ばれるクラスを生成するだろう。具象クラスを継承したモックを作ることもあるが、これは稀であり、一般的にはバッドデザインと見なされることだ。

筆者注:私はMockAccountよりもAccountMock(という命名)を好む。なぜならほとんどのIDE[統合開発環境]でアルファベット順でAccountの次にくるし、コード補完を使うときや、本体のコードと同じロケーションにテストコードを置く管理方法を選んだときに、手短にすますことができる。

ここにAccountインターフェースの簡単なサンプルがある

 
public interface Account
{
	public void addEntry( Date date, double amount );
	public List getEntriesFor( int month, int year );
	public double getBalance();
	・・・
}

The mock object for this interface would have the following methods:

このインターフェースのモックは下のようなメソッドを持つことになるだろう。

 
public class AccountMock implements Account{
   public void addEntry(AccountEntry arg0);
   ublic void setupExceptionAddEntry(Throwable arg);
   public void setExpectedAddEntryCalls(int calls);
   public void addExpectedAddEntryValues(AccountEntry arg0);

   public List getEntriesFor(int arg0, int arg1);
   public void setExpectedGetEntriesForCalls(int calls);
   public void addExpectedGetEntriesForValues(int arg0, int arg1);
   public void setupExceptionGetEntriesFor(Throwable arg);
   public void setupGetEntriesFor(List arg);

   public double getBalance();
   public void setupExceptionGetBalance(Throwable arg);
   public void setupGetBalance(double arg); 
   public void setExpectedGetBalanceCalls(int calls);
   ・・・
   public void verify();
}

Now, suppose you have a ReportFactory class and you want to write a method called createMonthlyReport that takes an account, a month, and a year, and produces a report. If you are an interaction-based unit tester, you would opt for using a mock Account object. If you are a state-based unit tester, you would opt for a real Account object in order to decouple your test from your implementation. If the Account object required too much context, making it difficult to create in the testing environment, you would create a stub.
Here's an interaction-based unit test for the createMonthlyReport object that uses a mock:

さて、ReportFactoryクラスがあって、月と年と勘定の情報を受け取りレポートを作成するcreateMonthlyReportと呼ばれるメソッドを作るとしよう。もし相互作用中心のユニットテストを行うならば、Accountのモックオブジェクトを用いるのを選択するだろう。もし状態中心のユニットテストを行うならば、実装をテストから切り離すために実際のAccountオブジェクトを選ぶだろう。もしAccountオブジェクトが非常に多くのコンテクスト[ありえる状態とそれを決める前後関係]を必要とし、テスト環境を作るのが困難ならば、スタブを作ることになるだろう。

createMonthlyReportに対する、モックを使った相互作用中心のユニットテストはこうだ。

public void testCreateMonthlyReport(){
	//setup
	AccountMock account = new AccountMock();
	account.setExpectedGetBalanceCalls(1);
	account.setupGetBalance(55.45);
	// ... etc
	int month = 2;
	int year = 1999;
	//execute
	Report actualReport = 
		ReportFactory.createMonthlyReport( month, year, account ); 
	//verify
	assertEquals( expectedReport, actualReport );
	account.verify();
}

As you can see, the setup section creates an AccountMock and populates it with expected values and values that it should return when certain methods are called. The execute section calls the method being tested and passes in the mock as an argument. The verify method checks that the method has returned the correct return value. The verify method is then called upon to ensure that the correct calls were made to the mock object.
Here is a state-based unit test for the same method, using a stub:

見て分かるように、setupの部分ではAccountMockを生成し、予期される値と、一定のメソッドが呼ばれた時に返されるはずの値とを持たせている。executeの部分ではテストされているメソッドを呼び出し、引数に生成したモックを渡している。verifyメソッドはこのメソッドが正しく値を返したかをチェックする。verifyメソッドはこうしてモックオブジェクトに対して正しい呼び出しがされたかを保証する責任を持つことになる。

同じメソッドに対する、スタブを使った状態中心のユニットテストはこうなる。

public void testCreateMonthlyReport() {
	//setup
	Account account = new Account() {
 public double getBalance() {
	return 5.12;
 }
		// ... etc
};
	int month = 2;
	int year = 1999;
	//execute
	Report actualReport = 
		ReportFactory.createMonthlyReport( month, year, account ); 
	//verify
	assertEquals( expectedReport, actualReport );
}

This time, the setup section creates an Account object by using an anonymous inner class and implementing the entire Account interface. There are several options that are better approaches if the Account interface is rather large. One is to use a regular inner class or a top-level class for your Account stub. This may be used by more than one test, so this isn’t so bad. Another method is to use a pattern called ObjectMother. This returns a real object with default values and allows you to change those values using setter methods. This works well for mutable objects, but not so well for immutable objects. A third way is to use pseudo objects, which are explained next.

今度は、setupのところで匿名クラスのAccountオブジェクトを生成している。そしてこれはAccountインターフェース全体を実装する。Accountインターフェースがかなり大きい場合には、より良い方法が幾つか考えられる。一つはAccountスタブに通常の内部クラスかトップレベルクラスを使う方法だ。これは一つのテスト以外にも使われるかもしれないので、それほど悪くない方法だ。別のやり方は、オブジェクトマザーと呼ばれるパターンを使うことだ。これは実際のオブジェクトをデフォルトの値と共に返してくれ、テストする時にはセッターメソッドを使って値を変えることができる。これはmutable[内部の状態などを変えられる]オブジェクトには使えるが、immutable[一度生成すると変更ができない]オブジェクトにはうまくいかない。3つ目の方法は、擬似オブジェクトを使う方法だ。これはこの後で説明する。

Page 2 of 4