apache wicketのユニットテスト機能
TRANSCRIPT
ユニットテスト とは"...ユニットテストでは、対象のクラスやメソッドが期待された振る舞いをするか検証し、テストが成功することによってそれを保証します。この「期待された振る舞い」とは、言い換えれば、対象のクラスやメソッドの仕様です。"
(渡辺修司, "JUnit実践入門 体系的に学ぶユニットテストの技法", 技術評論社, 初版, pp.29)
"...ユニットテストを繰り返し何度も実行することで、プログラムに問題が発生したときに、早い段階で影響範囲などをチェックできます。...リスク回避のために避けてきた機能拡張やコードの修正を安心して行う事ができます。"(渡辺修司, "JUnit実践入門 体系的に学ぶユニットテストの技法", 技術評論社, 初版, pp.30-31)
public class Calculator { public int add(int x, int y) { return x + y; } }
public class CalculatorTest { private Calculator calculator; @Before public void setUp() { calculator = new Calculator(); } @Test public void addメソッドで0足す1が1になる() { int actual = calculator.add(0, 1); assertThat(actual, is(1)); }
@Test public void addメソッドで3足す4が7になる() { int actual = calculator.add(3, 7); assertThat(actual, is(7)); } }
期待通りの振る舞いをしているかプログラムで自動的に確認
WicketTesternew WicketTester(WebApplication);
引数に渡されたWebApplicationのインスタンスをもとに、 プロジェクトがあたかもWebサーバ上で動いている様な
テスト用の実行環境とメソッドが提供される。 JUnitテストクラスのフィールド変数に定義しておくと楽。
private WicketTester tester; @Before public void setUp(){ tester = new WicketTester(new WicketApplication()); }
使用例
WicketTester#startPagetester.startPage(WebPage);
引数に渡されたページのテストが開始(ページが表示*)される。 各テストを行う前の準備段階として実行する。
引数は、ページのインスタンスもしくはクラスでもよい。
@Test public void Test1() { // ページのテスト開始(ページの表示) tester.startPage(new HomePage()); }
使用例
*ここでの「表示」はもちろん仮想的な意味であって、ブラウザでアクセスできるわけではない
WicketTester #assertRenderedPage
tester.assertRenderedPage(Class);
最後に表示されるページのクラスが引数と一致するかがassertされる。 ページが正常に描画されたかどうか、という意味でのテストに加え、 後述するLinkやFormのページ遷移結果のテストにも用いる。 スーパークラスが渡された場合も一致と見なされるので注意。
@Test public void 初期状態でページが表示される() {
// HomePageのテストの開始(ページの表示) tester.startPage(new HomePage()); // 画面に表示されるページが HomePage.class のページか tester.assertRenderedPage(HomePage.class); }
使用例
assert
WicketTester#assertLabeltester.assertLabel("id", String);
idのラベルに表示されている文字列がStringと一致するかがassertされる。
@Test public void ラベルに正しく文字列が表示される(){ tester.startPage(HomePage.class); //message1に こんにちは! が表示されるか
tester.assertLabel("message1", "こんにちは!"); }
使用例
assert
WicketTester#executeUrltester.executeUrl("./foo/bar");
引数に渡されたCleanURLのページが表示(テストが開始)される。(つまり、startPageのCleanURL版)
@Test public void パラメータありのCleanURLでページが表示される() {
// URLで表示されるページのテストの開始(ページの表示) tester.executeUrl("./foo/communityId/123"); // 画面に表示されるページが FooPage.class のページか tester.assertRenderedPage(FooPage.class); // communityIdのラベルが1000と表示されるか tester.assertLabel("communityId", "123"); }
使用例
WicketTester#clickLinktester.clickLink("id", boolean);
idのLinKコンポーネントのonClickメソッドが実行される。 クリックされた後の結果を、他のassertメソッドでチェックする。 第2引数はAjaxが有効な環境でのクリックかどうか(省略するとtrue)
@Test public void BarPageに遷移される() { tester.startPage(new WS01IndexPage()); // toBarPage Linkコンポーネントがクリックされる tester.clickLink("toBarPage"); // SimplePageクラスのページに遷移されるか tester.assertRenderedPage(BarPage.class); }
使用例
WicketTester コンポーネントの状態のassert
// idのコンポーネントが表示状態か tester.assertVisible("id"); // idのコンポーネントが非表示か tester.assertInvisible("id"); // idのコンポーネントが有効か tester.assertEnabled("id"); // idのコンポーネントが無効か tester.assertDisabled("id"); // idのコンポーネントが入力必須か tester.assertRequired("id")
それぞれ、idのコンポーネントの状態がassertされる。
assert
WicketTester #assertComponentOnAjaxResponsetester.assertComponentOnAjaxResponse("id");
idのコンポーネントがAjaxで更新(AjaxRequestTarget#addに追加)されたかがassertされる。
@Test public void link押下でwmcが非表示にされる() { tester.startPage(new HomePage()); tester.clickLink("link"); // linkクリックでAjaxでwmcが更新されるか tester.assertComponentOnAjaxResponse("wmc"); // wmcが非表示になったか tester.assertInVisible("wmc"); }
使用例
assert
WicketTester #executeAjaxEvent
tester.executeAjaxEvent("id", String);
idのコンポーネントでStringのイベントが実行される。 イベントは"onBlur"などのJSイベント。 ビヘイビアなどのテストに用いる。
@Test public void clickイベントでラベルが書き換わる(){ tester.startPage(HomePage.class); tester.assertLabel("label", "イベント発生前");
// Ajaxの click イベントを実行 tester.executeAjaxEvent("label", "click"); tester.assertLabel("label", "イベント発生後"); }
使用例
WicketTester #executeAllTimerBehaviorstester.executeAllTimerBehaviors(MarkupContainer);
引数のコンポーネント以下の全てのTimerBehaviorが実行される。 個別に実行したいときは、executeBehavior(Behavior)を使う。
@Test public void TimerBehaviorでclockが更新される() { WebPage page = tester.startPage(new AjaxTimerPage()); // PageのTimerBehaviorを全て動作させる. tester.executeAllTimerBehaviors(page); // TimerBehaviorがaddされたコンポーネントが更新されるか. tester.assertComponentOnAjaxResponse("clock"); }
使用例
WicketTester #startComponentInPage
tester.startComponentInPage(Component);
引数に渡したコンポーネントのテスト用の 実行環境とメソッドが提供される。
@Test public void Panelのラベルが正しく表示される() {
// コンポーネント(FooPanel)のテスト開始 tester.startComponentInPage(new FooPanel("foo")); // FooPanel内のLabel1が表示されるか tester.assertLabel("foo:label1", "Hello!")); }
使用例
注1)階層構造になっているwicket:idは、 id1:id2 の様にコロンで結合する 注2) 事前にページをスタートしておかなくても、コンポーネント単体でテストできる
WicketTester その他tester.assertComponent("id", Class);
idのコンポーネントのクラスが引数と一致するかがassertされる
assert
tester.assertModelValue("id", Object);
idのコンポーネントのモデルの値が引数と一致するかがassertされる
など。
tester.getLastRenderedPage(); tester.getComponentFromLastRenderedPage("id");
テスト中のページやコンポーネントを取得する
WicketTester#newFormTester
tester.newFormTester("form", false);
Formのテスト用の実行環境とメソッドが提供される。 第2引数は子のForm用コンポーネントの入力値を空にするかどうか
(省略するとtrue)
@Test public void FormTest1() { tester.startPage(new HomePage()); // Formのテスト開始 FormTester formTester = tester.newFormTester("form"); }
使用例
FormTester Form用コンポーネントの操作
formTester.setValue("id", String);
idのForm用コンポーネントにStringの値をセットする。 TextField, TextAreaなどのテキスト入力用。
formTester.select("id", int);
idのForm用コンポーネントのint番目を選択する。 RadioChoiceなどの単数選択用。
formTester.selectMultiple("id", int...);
idのForm用コンポーネントのint番目を全て選択する。 CheckBoxMultipleChoiceなどの複数選択用。
注)他に、ファイル送信用のsetFileや、個別のラジオボタン用のsetValueなどもある。
FormTester#submit
formTester.submit();
FormのonSubmitメソッドが実行される。
@Test public void FormをsubmitするとLabelに入力値が表示される() { tester.startPage(new FormPage()); // nameLabelは最初は空 tester.assertLabel("nameLabel", ""); FormTester formTester = tester.newFormTester("form"); formTester.setValue("name", "foo"); formTester.submit(); // nameLabelにフォームからsubmitされた入力値が表示される tester.assertLabel("nameLabel", "foo"); }
使用例
WicketTester #assertFeedback
tester.assertFeedback("id", String...);
assert
idのFeedbackPanelに表示されるメッセージが 引数のString配列と一致するかがassertされる。
なお、ページにセットされたメッセージをテストするときには、 assertInfoMessages(String...) assertErrorMessages (String...)
なども利用できる。
WicketTester#getSession
tester.getSession();
Sessionを取得できる。テストの前準備でSessionに値を設定したり、 assert用にテスト対象実行後のSessionの値を取得したりに使える。
@Test public void ログインしていなければエラーページが表示される() { MySession session = (MySession) tester.getSession(); session.setSigned(false); tester.startPage(SignedPage.class); tester.assertRenderedPage(ErrorPage.class); }
使用例
WicketTester 実際には
•実際のコードでは、モデルの生成やクリックイベントに他のクラスとの依存(DBとのやりとり、etc...)が絡んでくる。
•ユニットテストでは、こうしたコードをテストダブル(モックやスタブ)に置き換えてテストを実行する。
•WicketTesterでも同様に、テストダブルを用いたテストを実行できる。
•Wicketには Spring、Guice、CDIなどのDIコンテナとの正式な連係用APIがある。 DIコンテナを利用したい場合はこれも利用できる。
注)DIコンテナを利用するときは、JVMなどのメモリチューニングなどが必要なこともある(MaxPermSizeなど)
Guice、Springとの連係の例<dependency> <groupId>org.apache.wicket</groupId> <artifactId>wicket-‐guice</artifactId> <version>6.12.0</version> </dependency> <dependency> <groupId>com.google.inject</groupId> <artifactId>guice</artifactId> <version>3.0</version> </dependency>
Guiceの場合のpom.xml
<dependency> <groupId>org.apache.wicket</groupId> <artifactId>wicket-‐spring</artifactId> <version>6.12.0</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-‐context</artifactId> <version>3.2.5.RELEASE</version> </dependency>
Springの場合のpom.xml
Guiceとの連係の例@Override public void init() { super.init(); /* 中略 */ initGuice(); } protected void initGuice() { // Wicketから呼び出されたクラスのインジェクションを実行する getComponentInstantiationListeners().add(new GuiceComponentInjector(this)); }
WebApplicationのサブクラス
Guiceとの連係の例
public class BazPage extends WebPage { //Guiceでインジェクションしたい変数に@Inject @Inject private IFoo foo; /* 中略 */ }
Wicketのページやコンポーネント
// 標準でバインドされる実装クラスを設定しておく@ImplementeBy @ImplementedBy(Foo.class) public interface IFoo { public boolean bar(); /* 中略 */ }
インジェクションの対象になるインターフェースなど
通常利用するなら、設定はこれだけ。
Guiceとの連係の例(テストの準備)テスト用のクラス@Before public void setUp() {
tester = new WicketTester(new WicketApplication() { @Override protected void initGuice() {
// テストダブル用のModuleを作成 Module module = new Module() { @Override public void configure(Binder binder) { IFoo foo = mock(IFoo.class); // IFooクラスにテストダブルをバインドする binder.bind(IFoo.class).toInstance(foo); } };
getComponentInstantiationListeners().add(new GuiceComponentInjector(this, module)); } }); }
匿名クラスでWebApplicationのサブクラスの initGuiceメソッドをテスト用に拡張
テストダブルが優先してインジェクションされるようにする↓
←テストダブルを設定
ユニットテスト自体の方法やメソッドは変わらず。
Springとの連係の例
@Override public void init() { /* 中略 */ initSpring(); } protected void initSpring() { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); // アノテーション付きのBeanを下のパッケージから検索する ctx.scan("com.exmaple"); ctx.refresh(); getComponentInstantiationListeners().add(new SpringComponentInjector(this, ctx)); }
WebApplicationのサブクラス
注)アノテーションでのbean定義を行う場合。 ApplicationContextの設定次第で、従来のxmlでの定義なども勿論可能。
Springとの連係の例
public class BazPage extends WebPage { //Springでインジェクションしたい変数に@SpringBean @SpringBean private IFoo foo; /* 中略 */ }
Wicketのページやコンポーネント
// Springのbean定義として登録する。@Componentなどでも勿論OK。 @Service public class Foo implements IFoo { @Override public boolean bar() { /* 実装は中略 */ } }
インターフェースの実装クラスなど
通常利用するなら、設定はこれだけ。
Springとの連係の例(テストの準備)テスト用のクラス@Before public void setUp() {
tester = new WicketTester(new SpringApplication() { @Override protected void initSpring() {
// テストダブル作成 IFoo foo = Mockito.mock(IFoo.class); // テストダブル用のApplicationContextMockを用意し、テストダブルを追加 ApplicationContextMock ctxm = new ApplicationContextMock(); ctxm.putBean("foo", foo);
getComponentInstantiationListeners().add(new SpringComponentInjector(this, ctxm)); } });
}
匿名クラスでWebApplicationのサブクラスの initSpringメソッドをテスト用に拡張
テストダブルが優先してインジェクションされるようにする↑
←テストダブルを を設定
ユニットテスト自体の方法やメソッドは変わらず。