App Engineのユニットテストにて任意の例外発生を試す
タスクチェインの実装をしていると、テスト環境でもDEE発生をシミュレーションしたくなるのが人情というものです。
shin1ogawaさんのエントリでは下記のように説明されています。
テストのためにAppEngine環境を起動するには、大きくわけると
ApiProxy.setEnvironmentForCurrentThread()
ApiProxy.setDelegate()
のふたつの処理が必要になります。
ApiProxy.setEnvironmentForCurrentThread(ApiProxy.Environment)AppEngineの実行環境ではスレッドごとにApiProxy.Environmentのインスタンスが必要となるので、 AppEngineの実行環境がApiProxy#getCurrentEnvironment()を経由してApiProxy.Environmentのインスタンスを取得できるように設定する必要があります。 プロダクション環境や開発用のWebコンテナ経由で起動した場合はこれが自動的に設定されますが、それらを経由せず起動する場合は独自にインスタンスを作成・設定してやる必要があります。
404 shin1のつぶやき ないわー Not Found: AppEngine用のアプリケーションの自動テストについて(1)
Slim3では、このあたりのことをAppEngineTesterがやってくれています。
AppEngineTesterが無いとどうなるのか試す
AppEngineTesterの役目を知るには、AppEngineTesterを使わないでみるのが一番です。
quickstartのSlim3Serviceのテストケースから、AppEngineTesterをコメントアウトします。
package slim3sandbox.service; import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.slim3.tester.AppEngineTester; public class Slim3ServiceTest { @Test public void slim3test1() { Slim3Service.newAndPut("abc"); assertThat(Slim3Service.queryAll().size(), is(equalTo(1))); } private static AppEngineTester testHelper; @Before public void setUp() throws Exception { //testHelper = new AppEngineTester(); //testHelper.setUp(); } @After public void tearDown() throws Exception { //testHelper.tearDown(); } }
上記を実行すると、下記のトレース出力。
java.lang.NullPointerException: No API environment is registered for this thread.
at com.google.appengine.api.datastore.DatastoreApiHelper.getCurrentAppId(DatastoreApiHelper.java:74)
at com.google.appengine.api.datastore.DatastoreApiHelper.getCurrentAppIdNamespace(DatastoreApiHelper.java:84)
at com.google.appengine.api.datastore.Key.(Key.java:104)
at com.google.appengine.api.datastore.Key.(Key.java:92)
at com.google.appengine.api.datastore.KeyFactory.createKey(KeyFactory.java:72)
at com.google.appengine.api.datastore.DatastoreServiceImpl.buildAllocateIdsRef(DatastoreServiceImpl.java:414)
at com.google.appengine.api.datastore.DatastoreServiceImpl.allocateIds(DatastoreServiceImpl.java:400)
at com.google.appengine.api.datastore.DatastoreServiceImpl.allocateIds(DatastoreServiceImpl.java:387)
at org.slim3.datastore.DatastoreUtil.allocateIds(DatastoreUtil.java:283)
at org.slim3.datastore.DatastoreUtil.allocateId(DatastoreUtil.java:229)
at org.slim3.datastore.Datastore.allocateId(Datastore.java:138)
at org.slim3.datastore.ModelMeta.assignKeyIfNecessary(ModelMeta.java:276)
at slim3sandbox.meta.Slim3ModelMeta.prePut(Slim3ModelMeta.java:78)
at org.slim3.datastore.DatastoreUtil.modelToEntity(DatastoreUtil.java:1642)
at org.slim3.datastore.Datastore.put(Datastore.java:2195)
at slim3sandbox.service.Slim3Service.newAndPut(Slim3Service.java:19)
at slim3sandbox.service.Slim3ServiceTest.slim3test1(Slim3ServiceTest.java:15)
(略)
Environmentがないよ、ということです。空実装のApiProxy.Environmentをセットして、再度実行します。
@Before public void setUp() throws Exception { ApiProxy.setEnvironmentForCurrentThread(new ApiProxy.Environment() { @Override public Map<String, Object> getAttributes() { // TODO Auto-generated method stub return Maps.newHashMap(); } @Override public String getAppId() { // TODO Auto-generated method stub return ""; } //以下略 });
com.google.apphosting.api.ApiProxy$CallNotFoundException: The API package 'datastore_v3' or call 'AllocateIds()' was not found.
at com.google.apphosting.api.ApiProxy.makeSyncCall(ApiProxy.java:95)
at com.google.appengine.api.datastore.DatastoreApiHelper.makeSyncCall(DatastoreApiHelper.java:58)
at com.google.appengine.api.datastore.DatastoreServiceImpl.allocateIds(DatastoreServiceImpl.java:404)
at com.google.appengine.api.datastore.DatastoreServiceImpl.allocateIds(DatastoreServiceImpl.java:387)
at org.slim3.datastore.DatastoreUtil.allocateIds(DatastoreUtil.java:283)
at org.slim3.datastore.DatastoreUtil.allocateId(DatastoreUtil.java:229)
at org.slim3.datastore.Datastore.allocateId(Datastore.java:138)
at org.slim3.datastore.ModelMeta.assignKeyIfNecessary(ModelMeta.java:276)
at slim3sandbox.meta.Slim3ModelMeta.prePut(Slim3ModelMeta.java:78)
at org.slim3.datastore.DatastoreUtil.modelToEntity(DatastoreUtil.java:1642)
at org.slim3.datastore.Datastore.put(Datastore.java:2195)
at slim3sandbox.service.Slim3Service.newAndPut(Slim3Service.java:19)
at slim3sandbox.service.Slim3ServiceTest.slim3test1(Slim3ServiceTest.java:20)
(略)
makeSyncCallを呼び出してるっぽいです。ApiProxyに、先ほどのEnvironmentに加え空実装のDelegateをセットしてみます。
ApiProxy.setDelegate(new ApiProxy.Delegate<Environment>() { @Override public void log(Environment arg0, LogRecord arg1) { // TODO Auto-generated method stub } @Override public Future<byte[]> makeAsyncCall(Environment arg0, String arg1, String arg2, byte[] arg3, ApiConfig arg4) { // TODO Auto-generated method stub return null; } @Override public byte[] makeSyncCall(Environment arg0, String arg1, String arg2, byte[] arg3) throws ApiProxyException { // TODO Auto-generated method stub return null; } });
上記を実行すると、グリーンになりました。
上記を踏まえてAppEngineTesterの中身を見てみます。AppEngineTesterは、Delegateをimplementしています。
makeSyncCallをオーバーライドすれば、任意の例外を発生させたりできそうです。
下記のようなコードを書いてみます。
package slim3sandbox.service; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.slim3.tester.AppEngineTester; import com.google.apphosting.api.DeadlineExceededException; import com.google.apphosting.api.ApiProxy.ApiProxyException; import com.google.apphosting.api.ApiProxy.Environment; public class Slim3ServiceTest { @Test(expected=DeadlineExceededException.class) public void slim3test1() { Slim3Service.newAndPut("abc"); } private static AppEngineTester testHelper; @Before public void setUp() throws Exception { testHelper = new AppEngineTester() { @Override public byte[] makeSyncCall(Environment env, String service, String method, byte[] requestBuf) throws ApiProxyException { throw new DeadlineExceededException(); } }; testHelper.setUp(); } @After public void tearDown() throws Exception { testHelper.tearDown(); } }
グリーンになりました。
とりあえず、任意の例外を発生させるテストケースが書けるようになりました。