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

Passing in Mocks and Pseudos

モックと擬似オブジェクトでテストをパスする

It's often difficult to test methods that are responsible for their own object creation. If a method is responsible for creating the objects that it uses, then the method cannot be tested with substituted objects such as mock and stubs. The method must be refactored to separate the creation of the objects from the operations on those objects.

There are two primary refactorings that I have seen used to solve this problem. They both attempt to separate creational and behavioral functionality. This section starts with a simple example, and then you'll walk through both refactoring strategies so that you can see the resulting code and corresponding unit test.

The first refactoring strategy is documented in an article on IBM DeveloperWorks by Alex Chaffee and William Pietri. In their article's example, you have a run() method that creates and uses a View object:

処理で使うオブジェクトの生成について責務を持つメソッドのテストがやりづらくなることはよくあることだ。もしあるメソッドがその中での使うオブジェクトを生成するのに責務を持つとしたら、そのメソッドはモックやスタブといった代替オブジェクトでテストすることはできない。そのメソッドはオブジェクトに対する操作からオブジェクトの生成を切り離すようにリファクタリングする必要がある。

私が今まで見てきた中でも重要な2つのリファクタリングはこの問題を解決してくれる。どちらも生成と動作機能を切り離そうとする。このセクションでは簡単な例から始める。それから、できあがったコードと単体テストに対応するところを理解できるように、この両方のリファクタリングを確認していこう。

public class Application {
	public void run() {
		View v = new View();
		v.display();
	}
}

The article demonstrates testing the run method by refactoring the code through extracting a factory method:

ここでは、runメソッドをファクトリメソッドを介するようにコードをリファクタリングすることでテストする実例を示す。

public class Application {
	public void run() {
		View v = createView();
		v.display();
	}

	protected View createView() {
		return new View();
	}
}

Next, you can write a testcase that overrides the createView() method and passes in a mock or a stub instead.

次に、createView()メソッドをオーバーライドするテストケースを作り、モックまたは代わりのスタブを渡す。

class ApplicationTest extends TestCase {

	private MockView mockView = new MockView();

	public void testApplication() {
		Application a = new Application() {
			protected View createView() {
				return mockView;
			}
		};
		a.run();
		mockView.validate();
	}
}

When the run() method is called, the MockView is used instead of the real View object. This allows you to substitute mock objects for real objects, but not without a cost:

  • You can't make your class final because the test class needs to override it.
  • You have to create a new factory method for every single object that the method under test needs to use.

A second and slightly simpler way to test this is to start with a simple refactoring for the original Application class:

run()メソッドが呼ばれる時、MockViewは実際のViewオブジェクトの変わりに用いられる。これでモックオブジェクトを実際のオブジェクトの代わりにできるが、代償はある。

  • テストクラスはオーバーライドしないといけないので元のクラスをfinalで宣言できない
  • テストに必要な全てのオブジェクトに対して新しいファクトリーメソッドを作らないといけない

これをテストする2つめの少しシンプルな方法は、元のApplicationクラスについてシンプルなリファクタリングするとこから始める。

public class Application {
	public void run() {
		run(new View());
	}

	// package private for testing only
	void run(View view) {
		view.display();
	}
}

Make the public run method a pass-through to a package-private run method. The public run method will still be called by production code in the same exact way, though it doesn't do anything besides create the real View object and pass it in to the package-private run method. The package-private run method operates on the objects, but is free of creational responsibilities. This method remains package-private so that the test class can access it.
Here is the test:

このpublicなrunメソッドをpackage-privateなrunメソッドへのパススルーとするのだ。publicなrunメソッドは、変わらず本体のコードから正しい方法で呼ばれるが、実際のViewオブジェクトを生成してpackage-privateなrunメソッドに渡す以外には何もしていない。puckage-privateなrunメソッドがオブジェクトへの操作を行うことになるが、生成の責任からは解放されている。このメソッドはテストクラスでもアクセスできるようにpackage-privateにされている。
テストはこうなる。

class ApplicationTest extends TestCase {

	private MockView mockView = new MockView();

	public void testApplication() {
		Application application = new Application();
		application.run(mockView);
		mockView.validate();
	}
}

Now, all you have to do is test the package-private run method that takes a parameter. The test is focused on just the behavioral aspect of the class. You don't have to override anything, and you can make the class final if you so desire. If the run method needs more than one object, simply make the public run method create multiple objects, and make the "testable" run method take multiple arguments. There is no need to test the run method that takes no parameters because it has no behavioral responsibilities that need testing.
Another method of accomplishing the same thing is to make the client code responsible for creating the objects. You can achieve this by creating all the necessary objects in the client code and then passing them in through either the constructor or through setter methods. This is often referred to as dependency injection. There are dependency injection frameworks that make this job easy. I have written a two-part article on a dependency injection framework called Spring. The articles can be found here on DevX:

Spring: Creating Objects So You Don't Have To
Simplify Your Web App Development Using the Spring MVC Framework

さて、やるべきことはパラメータを受け取るpackage-priavteなrunメソッドをテストすることだけだ。このテストはクラスの動作面のみに焦点をあてる。なにもオーバーライドする必要はないし、お望みであればfinalでクラスを宣言することもできる。もしrunメソッドが2つ以上のオブジェクトを必要とするなら、ただpublicなrunメソッドの方で複数のオブジェクトを生成するようにして、"テスト用"のrunメソッドは複数の引数を受け取るようにすればいいだけだ。引数を持たない方のrunメソッドは、テストが必要な振る舞いについての責任を持たないので、テストをする必要がない。
同じ事を実現する別の方法は、[上に挙げたような動作について責任を持つメソッドではなく、]オブジェクトを生成する責任のあるクライアントコードを作ることだ。必要なオブジェクトを全てをクライアントコードで作り、コンストラクタかセッターメソッドに渡すようにするのだ。これはよくディペンデンシ・インジェクション[依存性注入]と呼ばれ、ディペンデンシ・インジェクション・フレームワークはこの作業を簡単にできるようにしてくれる。私はSpringと呼ばれるディペンデンシ・インジェクション・フレームワークについて2部構成の記事を書いたことがある。この記事はここDevXで見ることができる。

Spring: 生成しなくてもいいようにオブジェクトを生成する
http://www.devx.com/Java/Article/21665/
SpringのMVCフレームワークを使ってWebアプリ開発を簡単にしよう
http://www.devx.com/Java/Article/22134

Page 4 of 4