appengineのjunitテストを実行環境によって動かしたり動かさなかったりしたい

うーん。いろいろ悩ましいのですがとりあえず書いておこうと思います。


production環境でのテストには、@bufferings氏のkotoriが使えるので、便利です。

便利ですが、僕の作っているbalmysundaycandyというものでは、low level apiが隠蔽しているappengineの割と中のほうの隠れapi的なものを使うようにするぜ!というもので、環境によって動いたり動かなかったりということがあります。

例えば、datastore_v3#GetSchemeは、開発環境では動作しますが、プロダクション環境では動作しません。こういう隠れapiでなくても、例えば、kindless ancestor queryと呼ばれているクエリは、プロダクション環境では動作しますが開発環境では動作しません。これについては、@shin1ogawaさんの解決方法があるので、まぁどうにかできる問題ではあります。

そもそも開発環境とプロダクションでapiが異なるという状況はそのうち解決されるはずであり、言うなればgoogleの優先順位付けの問題で時間が経てば開発環境でもプロダクション環境でも同じものが動くはず。と思います。

今できるできないはさておき、今普通にやるとできないことを普通にやるかのようにやれるようにするのがbalmysundaycandyの目標になりつつあります。なので、開発環境でだけでも動くならそれはありで、プロダクションでだけ動くのならそれもありです。とはいえ、単体テストはないと困ります。バグリそうなところをカバーするパスを通るような結合サンプルを書くのは大変そうだし、appengineのバージョンアップに対応して全パスたたき直しとか仕事でもやりたくないです。


ということで、あまりこういう需要はないかもしれませんが、方法を考えてみました。


アプリケーションの動作環境は、

System.getProperty("com.google.appengine.runtime.environment")

で取得することができ、eclipseからjunitをたたいているのであればnull、開発環境でコンテナを起動しているとDevelopment、プロダクションにデプロイしているとProductionを戻してくれます。

ということで、動作環境の切り分けは容易で、こういう感じにしています。

さて、これを使ってテストケースを切り分ければ実装できるのですが、さすがにif文で切り分けるのはダサイです。


kotoriでは、テストケースの検知をjunitのバージョンによって切り分けていて、junit4ならこんな感じでやっています。

ここに適当なアノテーションがついていたらテスト対象にする・しないをきれば、テストケースにそのアノテーションをふればいいことになります。それで書いてみて、

public class TestCaseChecker4 {
  /** {@inheritDoc} */
  public boolean checkAndAddTestCase(TestStore store, Class<?> c) {
    TestSuiteModel suite = null;
    Method[] methods = c.getDeclaredMethods();
    for (Method m : methods) {
      // public only
      if (m.getModifiers() == Modifier.PUBLIC
        && m.getAnnotation(Test.class) != null
        && m.getAnnotation(Ignore.class) == null
        && isTestTargetDetectWorkingEnvironment(m)) {
        if (suite == null) {
          suite = new TestSuiteModel(c.getCanonicalName(), c.getSimpleName());
          store.add(suite);
        }
        store.add(suite.getKey(), new TestCaseModel(c.getCanonicalName(), m
          .getName()));
      }
    }
    return suite != null;
  }
  
  /**
   * detect current working environment and check the method should be test target.
   * 
   * @param m method
   * @return the method should be test target.
   */
  private static boolean isTestTargetDetectWorkingEnvironment(Method m){
	  if (m.getAnnotation(DevlopmentEnvironmentTest.class) != null){
		  return WorkingEnvironment.isNocontainer() || WorkingEnvironment.isDevelopment();
	  } else if (m.getAnnotation(ProductionEnvironmentTest.class) != null) {
		  return WorkingEnvironment.isProduction();
	  } else {
		  // if not annotated, the method will be test target.
		  return true;
	  }
  }
}

というようにしてみました。

テストは、

public class JUnit4Sample3 {

	@Test
	public void notDefinedTestTarget() {
		// attached in anywhere
		assertTrue(true);
	}

	@Test
	@DevlopmentEnvironmentTest
	public void definedRunInDevelopmentEnvironment() {
		assertThat(WorkingEnvironment.isDevelopment(), is(true));
	}

	@Test
	@ProductionEnvironmentTest
	public void definedRunInProductionEnvironment() {
		assertThat(WorkingEnvironment.isProduction(), is(true));
	}
}

などとすると、definedRunInDevelopmentEnvironmentは開発環境でktr-wjrを起動したときのみテスト対象に、definedRunInProductionEnvironmentはプロダクションにktr-wjrを含むテストケースをデプロイして実行したときのみ対象になります。プロダクションでですが、ここだと、ちゃんとdefinedRunInDevelopmentEnvironmentがテスト対象から外れています。


とりあえず当初の目的は達成されたのですが、これだとeclipseから動かしたときにどっちもテスト対象になってしまいます。kotoriにしか手を加えていないので。これは困ります。


ということで方法を調べ中なのですが、実は最初からjunit4.8から追加されたcategoryを使用することを考えていました。がんばればこれをつかってどうにかできそうなのですが、僕が試した範囲だと、TestSuiteを個別に作成して、

@RunWith(Categories.class)
@IncludeCategory(DevlopmentEnvironmentTest.class)
@SuiteClasses( { JUnit4Sample3.class})
public class TestSuite {
}
@Test
@Category(DevlopmentEnvironmentTest.class)
public void definedRunInDevelopmentEnvironment() {
	assertThat(WorkingEnvironment.isDevelopment(), is(true));
}

とすることになります。TestSuiteを動作環境に対応させるように定義しないといけないのが面倒です。eclipseが使っているjunit runnerがどうなってるのかがわからないので何ともですが、4.8.1にはまだ対応していないんじゃないかと。というか、僕のeclipseが20090920-1017ビルドでjunit4.8.1は2009/12/08リリースの様なので、そりゃ対応されてないよね。

動かすカテゴリをeclipseから決めてテストを実行できるようにする、の実装でよい気がします。

ということで、いまのところお蔵入りになりそうな予感。

追記:
というようなことを考えていたら、@bufferingsさんが解決策を出してくれました!
AppEngine の実行環境の判別を Assume でやってみる

シンプルでいい感じだと思います!category指定ができるeclipseのpluginを作らなければならないと思っていたので難しく考え過ぎだったようです。

でも、リモートでデプロイしてテスト実行までできるプラグインがあったら便利だなぁとか。