Spring Framework上で動くサービスをJUnitでテストすると、自分の意図しない動きをすることがあって、お家で調査。お家だと、Webサイトのアクセス制限とかないし(GitHubとかにもアクセスできるw)、ネットワークの帯域の制限もないし(ダウンロードしほうだい)、PCのスペック高いからEclipseとかサクサク動くし(しょっちゅう固まったりしない)、画面の解像度高いし(調査時間の半分はスクロールだったりしない)。はっきりいって、圧倒的に効率がよい。
職場は仕方ないの、、限られた資源をみんなで分け合うから。。優秀な技術者に、良い環境を与えてあげてください。
さ、気を取り直して。
EclipseでMavenプロジェクトを作って、設定でソースコードのダウンロードをオンにして、pom.xml
に必要なアプリ(とりあえず以下)を追加してやって、Mavenの「プロジェクトの更新」を実行すれば、準備オッケー。1文。
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.1.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>4.1.6.RELEASE</version> </dependency> </dependencies>
で、いくつかのファイルを、古典的なやり方で追加した。この方が、挙動がわかりやすいと思ったからだ。ただ最近のやり方に精通してないからではないかって?ご名答。完全に浦島太郎です。
ほぼほぼ「Hello World!」なのだが、HelloMessageData
クラスから、staticな文字列を拾ってくるところだけ、追加している。ちょっとうっとうしいが、各ファイルの中身を書いておく。
HelloMessageData.java
… staticな文字列を保持するだけのクラス。疑似データベースといったところ
package hello; public class HelloMessageData { private static String message = "Hello World!"; public static String getMessage() { return message; } public static void setMessage(String message) { HelloMessageData.message = message; } }
MessageService.java
… 文字列を取得するだけのインターフェース
package hello; public interface MessageService { String getMessage(); }
HelloMessageService.java
… 上記インターフェースの実装
package hello; public class HelloMessageService implements MessageService { private final String message; HelloMessageService() { this.message = HelloMessageData.getMessage(); } public String getMessage() { return message; } }
MessagePrinter.java
… メッセージサービス(MessageService
)を用いて取得した文字列を表示するクライアント
package hello; public class MessagePrinter { private MessageService messageService; public void setMessageService(MessageService messageService) { this.messageService = messageService; } public String getMessage() { return messageService.getMessage(); } public void pringMessage() { System.out.println(messageService.getMessage()); } }
applicationContext.xml
… 忌まわしき、設定ファイル
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd"> <bean id="messageService" class="hello.HelloMessageService" /> <bean id="messagePrinter" class="hello.MessagePrinter"> <property name="messageService" ref="messageService"/> </bean> </beans>
とまあ、こんな感じ。MessagePrinter
が、メッセージを表示するだけのクライアント。ここに MessageService
という(文字列を渡すだけの)インスタンスをインジェクションし、文字列を取得し、表示する。文字列は、MessageService
の実装がインスタンス化されるときにだけ、HelloMessageData
クラスから取得し、インスタンス内に保持する。
動かし方は、単純なメインクラスを用いるなら、例えば次のようになる。
Main.java
package main; import hello.MessagePrinter; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Main { public static void main(String[] str) { @SuppressWarnings("resource") ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); MessagePrinter printer = (MessagePrinter) context.getBean("messagePrinter"); printer.pringMessage(); } }
ここで、本題。MessagePrinter
クラスのテストを行う。とりあえず、Spring Frameworkが提供するクラスを用いず、素のJUnitで作成してみた。
HelloMessagePrinterTest.java
package hello; import static org.junit.Assert.*; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class HelloMessagePrinterTest { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); @Test public void test_getMessage1() { MessagePrinter printer = (MessagePrinter) context.getBean("messagePrinter"); assertEquals("Hello World!", printer.getMessage()); } /** * {@code ApplicationContext} がリフレッシュされていないので、"Hello World!"が * 取得される。 */ @Test public void test_getMessage2() { HelloMessageData.setMessage("Nice to meet you!"); MessagePrinter printer = (MessagePrinter) context.getBean("messagePrinter"); assertEquals("Hello World!", printer.getMessage()); } /** * {@code ApplicationContext} がリフレッシュされていないので、"Hello World!"が * 取得されるはずなのだが、"Nice to meet you!"が取得される。 */ @Test public void test_getMessage3() { HelloMessageData.setMessage("What's your Name?"); MessagePrinter printer = (MessagePrinter) context.getBean("messagePrinter"); assertEquals("Hello World!", printer.getMessage()); } }
アプリケーションコンテキストは、(テストクラスのインスタンスが生成されるときに)1度だけ生成されるはず。つまり、MessageService
のインスタンスは、文字列「Hello World!」を保持し、その後変更されないはず。なので、常に「Hello World!」という文字列が取得され、上記テストは3つとも成功するはず。だったのだが、そうではなかった。
最初の2つのテストは成功するのだが、最後のテストは、次のメッセージを残して失敗する。
org.junit.ComparisonFailure: expected:<[Hello World]!> but was:<[Nice to meet you]!>
テストの実行順序に依存することはわかっているのだが(例えば、3つ目のテストを単独で流すと成功する)。なぜだ。。
2015/05/24追記
Spring Framework のコンポーネントを使ったら、とりあえずテストが通った。謎は解けないままだが。
ちなみに、test-applicationContext.xml
の中身は、上記 applicationContext.xml
といっしょ。
package hello; import static org.junit.Assert.*; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("/test-applicationContext.xml") public class HelloMessagePrinterTest { @Autowired ApplicationContext context; @Test public void test_getMessage1() { MessagePrinter printer = (MessagePrinter) context.getBean("messagePrinter"); assertEquals("Hello World!", printer.getMessage()); } /** * {@code ApplicationContext} がリフレッシュされていないので、"Hello World!"が * 取得される。 */ @Test public void test_getMessage2() { HelloMessageData.setMessage("Nice to meet you!"); MessagePrinter printer = (MessagePrinter) context.getBean("messagePrinter"); assertEquals("Hello World!", printer.getMessage()); } /** * {@code ApplicationContext} がリフレッシュされていないので、"Hello World!"が * 取得される。 */ @Test public void test_getMessage3() { HelloMessageData.setMessage("What's your Name?"); MessagePrinter printer = (MessagePrinter) context.getBean("messagePrinter"); assertEquals("Hello World!", printer.getMessage()); } }