みけです。
いきさつ
私のATND notifier には単語「JUnit」が登録されているので、
誰かがJUnitというキーワードのあるイベントが登録すると自動通知するようになっています。
というわけで、参加してきました。
JUnit強化キャンプ
内容
非常に内容が濃いので、何回かにわけて報告しようと思います。
個人的にはJUnitの応用編が大変勉強になりました。
そして、主催の @shuji_w6e さんが実は熟練したGroovyistであることもわかりました。
Groovy…エ
ざっくりしたGroovyの紹介でした。
POGOをJavaで使う際に便利なアノテーションで
コンストラクタやtoStringなども自動生成できることを教えてもらいました。
例えば、このようなアノテーションを付与したクラス
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import groovy.transform.TupleConstructor | |
import groovy.transform.EqualsAndHashCode | |
import groovy.transform.ToString | |
@TupleConstructor | |
@EqualsAndHashCode | |
@ToString | |
class Dollar { | |
final double value | |
} |
は、このようにコンパイルされます。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import groovy.transform.EqualsAndHashCode as EqualsAndHashCode | |
import groovy.transform.TupleConstructor as TupleConstructor | |
import groovy.transform.ToString as ToString | |
@groovy.transform.TupleConstructor | |
@groovy.transform.EqualsAndHashCode | |
@groovy.transform.ToString | |
public class Dollar implements groovy.lang.GroovyObject extends java.lang.Object { | |
final private double value | |
private static org.codehaus.groovy.reflection.ClassInfo $staticClassInfo | |
public static transient boolean __$stMC | |
private transient groovy.lang.MetaClass metaClass | |
public static long __timeStamp | |
public static long __timeStamp__239_neverHappen1333850088031 | |
public Dollar(double value) { | |
metaClass = /*BytecodeExpression*/ | |
this .value = value | |
} | |
public Dollar() { | |
this (((0.0) as double)) | |
} | |
public int hashCode() { | |
java.lang.Object _result = org.codehaus.groovy.util.HashCodeHelper.initHash() | |
_result = org.codehaus.groovy.util.HashCodeHelper.updateHash( _result , this.getValue()) | |
return _result | |
} | |
public boolean canEqual(java.lang.Object other) { | |
return other instanceof Dollar | |
} | |
public boolean equals(java.lang.Object other) { | |
if ( other == null) { | |
return false | |
} | |
if (this.is(other)) { | |
return true | |
} | |
if ( other instanceof Dollar) { | |
} else { | |
return false | |
} | |
if (other.canEqual( this )) { | |
} else { | |
return false | |
} | |
java.lang.Object otherTyped = (( other ) as Dollar) | |
if (this.getValue() != otherTyped.getValue()) { | |
return false | |
} | |
return true | |
} | |
public java.lang.String toString() { | |
java.lang.Object _result = new java.lang.StringBuffer() | |
_result.append('Dollar') | |
_result.append('(') | |
_result.append(org.codehaus.groovy.runtime.InvokerHelper.toStringthis.getValue()) | |
_result.append(')') | |
return _result.toString() | |
} | |
public java.lang.Object this$dist$invoke$1(java.lang.String name, java.lang.Object args) { | |
return this."$name"(* args ) | |
} | |
public void this$dist$set$1(java.lang.String name, java.lang.Object value) { | |
this ."$name" = value | |
} | |
public java.lang.Object this$dist$get$1(java.lang.String name) { | |
return this ."$name" | |
} | |
protected groovy.lang.MetaClass $getStaticMetaClass() { | |
} | |
public groovy.lang.MetaClass getMetaClass() { | |
} | |
public void setMetaClass(groovy.lang.MetaClass mc) { | |
} | |
public java.lang.Object invokeMethod(java.lang.String method, java.lang.Object arguments) { | |
} | |
public java.lang.Object getProperty(java.lang.String property) { | |
} | |
public void setProperty(java.lang.String property, java.lang.Object value) { | |
} | |
public static void __$swapInit() { | |
} | |
static static { | |
__timeStamp__239_neverHappen1333850088031 = 0 | |
__timeStamp = 1333850088031 | |
} | |
final public double getValue() { | |
} | |
public void super$1$wait() { | |
} | |
public java.lang.String super$1$toString() { | |
} | |
public void super$1$wait(long param0) { | |
} | |
public void super$1$wait(long param0, int param1) { | |
} | |
public void super$1$notify() { | |
} | |
public void super$1$notifyAll() { | |
} | |
public java.lang.Class<java.lang.Object extends java.lang.Object> super$1$getClass() { | |
} | |
public java.lang.Object super$1$clone() { | |
} | |
public boolean super$1$equals(java.lang.Object param0) { | |
} | |
public int super$1$hashCode() { | |
} | |
public void super$1$finalize() { | |
} | |
} |
toStringやコンストラクターが自動で生成されるんですね。
AppengineTestCase
JUnitの応用編で教えてもらったRuleアノテーションの利用方法です。
Slim3にてテストクラスを作成する場合は、AppengineTestCaseクラスを継承して作成するのが一般的です。
そこで、下記のようなユーティリティクラスを作成します。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package org.mikeneck.gae.slim3.sample.util; | |
import org.junit.rules.ExternalResource; | |
import org.slim3.tester.AppEngineTester; | |
/** | |
* @author : mike | |
* @since : 12/04/08 | |
*/ | |
public class AppengineExternalResource extends ExternalResource { | |
private AppEngineTester tester = new AppEngineTester(); | |
@Override | |
protected void after() throws RuntimeException{ | |
try { | |
tester.tearDown(); | |
} catch (Exception e) { | |
e.printStackTrace(); | |
throw new RuntimeException(e); | |
} | |
} | |
@Override | |
protected void before() throws Throwable { | |
tester.setUp(); | |
} | |
} |
これは、単純にAppengineTestCaseクラスを真似したようなクラスです。
このユーティリティクラスにRuleアノテーションを付与して次のようにテストクラスを作成します。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package org.mikeneck.gae.slim3.sample; | |
import com.google.appengine.api.datastore.Key; | |
import org.junit.Rule; | |
import org.junit.Test; | |
import org.junit.rules.ExternalResource; | |
import org.mikeneck.gae.slim3.sample.util.AppengineExternalResource; | |
import org.slim3.datastore.Datastore; | |
import java.util.Date; | |
import java.util.List; | |
import static org.hamcrest.Matchers.is; | |
import static org.junit.Assert.assertThat; | |
/** | |
* @author : mike | |
* @since : 12/04/08 | |
*/ | |
public class MessageTest { | |
@Rule | |
public static ExternalResource resource = new AppengineExternalResource(); | |
private static final MessageMeta meta = MessageMeta.get(); | |
@Test | |
public void countData () { | |
Message message = new Message(); | |
message.setCreatedAt(new Date()); | |
message.setMessage("test"); | |
Key key = Datastore.put(message); | |
Message stored = Datastore.get(meta, key); | |
assertThat(stored.getMessage(), is("test")); | |
} | |
} |
すると、不思議なことにAppengineTestCaseを継承していなくてもテストクラスが作成できます。
これのお陰でBeforeアノテーションを付与したsetUpメソッドを別途作成することができ、super.setUp()を呼び出さなくても良くなります。
なお、ものにもよりますが、Ruleアノテーションを付与したExternalResourceのbeforeメソッドはBeforeアノテーションより前に実行されます。
明日、また内容についてブログを書きます。
それでは。
参考
junitにデフォルトであるExternalResourceクラス
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package org.junit.rules; | |
import org.junit.runner.Description; | |
import org.junit.runners.model.Statement; | |
/** | |
* A base class for Rules (like TemporaryFolder) that set up an external | |
* resource before a test (a file, socket, server, database connection, etc.), | |
* and guarantee to tear it down afterward: | |
* | |
* <pre> | |
* public static class UsesExternalResource { | |
* Server myServer= new Server(); | |
* | |
* @Rule | |
* public ExternalResource resource= new ExternalResource() { | |
* @Override | |
* protected void before() throws Throwable { | |
* myServer.connect(); | |
* }; | |
* | |
* @Override | |
* protected void after() { | |
* myServer.disconnect(); | |
* }; | |
* }; | |
* | |
* @Test | |
* public void testFoo() { | |
* new Client().run(myServer); | |
* } | |
* } | |
* </pre> | |
*/ | |
public abstract class ExternalResource implements TestRule { | |
public Statement apply(Statement base, Description description) { | |
return statement(base); | |
} | |
private Statement statement(final Statement base) { | |
return new Statement() { | |
@Override | |
public void evaluate() throws Throwable { | |
before(); | |
try { | |
base.evaluate(); | |
} finally { | |
after(); | |
} | |
} | |
}; | |
} | |
/** | |
* Override to set up your specific external resource. | |
* @throws if setup fails (which will disable {@code after} | |
*/ | |
protected void before() throws Throwable { | |
// do nothing | |
} | |
/** | |
* Override to tear down your specific external resource. | |
*/ | |
protected void after() { | |
// do nothing | |
} | |
} |
statementメソッドでbefore()→evaluate()→after()の順番で実行されています。
このevaluate()にて@Before→@Test→@Afterが実行されていきます。