Cucumber-JVMで、同じシナリオを異なるレイヤーでテストする

以前のブログでCucumber-JVMのテストを紹介したときは、ドメインクラス(Carクラス)のオブジェクトと直接やりとりする下位のレイヤーのプログラムのテストでしたが、同じシナリオを使って上位のレイヤーのテストも可能です。

例えば、以前のブログで紹介したCarクラスの操作が、RESTのAPIで提供されているとしましょう。まずは、シナリオを再確認します。テストに使用するシナリオは、以前と同じく以下の内容です。

Feature: 車の燃料の補給
  運転手は、車を走らせるために、燃料を補給する必要がある。

Scenario: 燃料の補給
 Given 車のタンクには、10リットルの燃料が入っている
 When 運転手は、50リットルの燃料を補給した
 Then タンクには、60リットルの燃料が入っている

次に、Step Definitionsの実装例を以下に示します。

public class CarMaintenanceSteps {
	
	private int carId;
	private RestTemplate restTemplate;// 初期化の処理の記述は省略
	
	@Given("^車のタンクには、(\\d+)リットルの燃料が入っている$")
	public void 車のタンクには_リットルの燃料が入っている(int arg1) throws Throwable {
		Car car = new Car(arg1);
		car = restTemplate.postForObject(
				"http://localhost:8080/carapp/car/add", car, Car.class);
		carId = car.getId();
	}

	@When("^運転手は、(\\d+)リットルの燃料を補給した$")
	public void 運転手は_リットルの燃料を補給した(int arg1) throws Throwable {
		restTemplate.postForObject(
				"http://localhost:8080/carapp/car/{carId}/fuel/add", arg1, Car.class, carId);
	}

	@Then("^タンクには、(\\d+)リットルの燃料が入っている$")
	public void タンクには_リットルの燃料が入っている(int arg1) throws Throwable {
		Car car = restTemplate.getForObject(
				"http://localhost:8080/carapp/car/{carId}", Car.class, carId);
		assertEquals(car.getFuelLevel(), arg1);
	}
}

RestTemplateは、Spring Frameworkが提供するクラスで、RESTのクライアントの機能を提供します(RestTemplateの使い方の説明は、今回の本筋ではないため割愛します。また、サーバ側の実装も本筋ではないため割愛します)。シナリオのステップの内容に合わせてURIを変更しRESTのAPIを呼出しているのが確認できます。

Webコンテナの起動

上記のStep Definitionsを実行する際は、RESTのAPIを提供するサーバを起動しておく必要があります。Javaの場合だとWebコンテナの起動が必要です。Webコンテナの起動は、テストの実行時に自動的に行えた方がよいです。

Cucumber-JVMでは、Hooksという機能を使って、シナリオの実行前と実行後に任意のメソッドを実行することができます。使い方は簡単で、シナリオの実行前に実行したいメソッドには@Beforeアノテーションを指定し、シナリオの実施後に実行したいメソッドには@Afterアノテーションを指定します(JUnitの@Beforeと@Afterとはパッケージ名が違います)。

シナリオの実行前にコンテナを起動したい場合は、@Beforeを指定したメソッドでコンテナを起動する処理を記述すれば良いです。以下にサンプルを示します。

public class HooksSample {
	private static Context context; 	
	@Before
	public void before() throws Exception {
		if (context == null) {
			Tomcat tomcat = new Tomcat();
			tomcat.setPort(8080);
			tomcat.setBaseDir(".");
			tomcat.getHost().setAppBase(".");
			tomcat.start();
			context = tomcat.addWebapp(tomcat.getHost(), "/carapp", "WebContent");
		} else {
			context.reload();
		}
	}
}

上記のサンプルでは、TomcatAPIを呼出してコンテナを起動しています。シナリオ毎にTomcatを起動するのは時間がかかるため、1回目のシナリオの実行時にTomcatを起動し、2回目以降はアプリケーションをリロードするだけになっています(TomcatAPIの使い方の説明は、今回の本筋ではないため割愛します)。

さいごに

Cucumber-JVMを使って、同じシナリオを、異なるレイヤーでテストできることを紹介しました。シナリオが軸になるため、テストコードの目的が統一され、精度とメンテナンス性が向上すると思います。