Spring Boot 1.3.x の Web アプリを 1.4.x へバージョンアップする ( その16 )( テストクラスのモックを @MockBean + Mockito で作り直す )
概要
記事一覧はこちらです。
Spring Boot 1.3.x の Web アプリを 1.4.x へバージョンアップする ( その15 )( テストクラスのアノテーションを 1.4 のものに変更する ) の続きです。
- 今回の手順で確認できるのは以下の内容です。
参照したサイト・書籍
Spring Boot Reference Guide - 40.3.4 Mocking and spying beans
http://docs.spring.io/spring-boot/docs/1.4.x/reference/htmlsingle/#boot-features-testing-spring-boot-applications-mocking-beansMockito
http://site.mockito.org/Mocking static methods with Mockito
http://stackoverflow.com/questions/21105403/mocking-static-methods-with-mockitoHow can I mock a private static method with PowerMockito?
http://stackoverflow.com/questions/31796736/how-can-i-mock-a-private-static-method-with-powermockitopowermock/powermock - MockitoUsage
https://github.com/powermock/powermock/wiki/MockitoUsageCan Mockito stub a method without regard to the argument?
http://stackoverflow.com/questions/5969630/can-mockito-stub-a-method-without-regard-to-the-argument
目次
- @MockBean アノテーションは Mockito を使うが、バージョンは 2 ではなく 1 らしい
- 変更手順を考える
- build.gradle を修正する
- clean タスク → Rebuild Project を実行して修正対象のソースを洗い出す
- LibraryHelperTest.java を修正する
- BooklistServiceTest.java を修正する
- InquiringStatusOfBookQueueListenerTest.java を修正する
- 全てのテストを実行してみる
- 感想&次回は。。。
手順
@MockBean アノテーションは Mockito を使うが、バージョンは 2 ではなく 1 らしい
40.3.4 Mocking and spying beans を読むと @MockBean は Mockito を利用してモックを作成するようです。
Mockito のホームページ を見ると “Current version is 2” と記述されており、jcenter で検索すると最新バージョンは 2.7.20 と表示されるのですが、Spring Boot Reference Guide の Appendix F. Dependency versions を見ると mockito-core のバージョンは 1.10.19 が記述されており、Athens-SR3 の Spring IO Platform Reference Guide の Appendix A. Dependency versions でも 1.10.19 でした。
Mockito のバージョン 2 系は使用できないのかな?と思って調べると、Spring Boot の以下の Issue が見つかりました。2 系が使えるのは 1.5 以降のようです。今回は 1 系を使うことにします。
- Upgrade to Mockito 2
https://github.com/spring-projects/spring-boot/issues/7770 - Document how to use Mockito 2 with Spring Boot 1.5
https://github.com/spring-projects/spring-boot/issues/8217
変更手順を考える
以下の手順で変更していきます。
- build.gradle を編集して JMockit を外して Mockito を追加します。
- clean タスク → Rebuild Project を実行してエラーが出る箇所 ( JMockit を利用している箇所 ) を洗い出します。
- 一旦 build.gradle に JMockit を戻します。
- エラーが出た箇所を @MockBean + Mockito で書き直します。
- build.gradle から JMockit を外します。
- 最後に全てのテストを通しで実行して成功することを確認します。
build.gradle を修正する
build.gradle を リンク先のその1内容 に変更します。
変更後、Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。
clean タスク → Rebuild Project を実行して修正対象のソースを洗い出す
clean タスク → Rebuild Project を実行すると以下のソースでエラーが出ました。
- src/test/java/ksbysample/webapp/lending/helper/library/LibraryHelperTest.java
- src/test/java/ksbysample/webapp/lending/web/booklist/BooklistServiceTest.java
- src/test/java/ksbysample/webapp/lending/listener/rabbitmq/InquiringStatusOfBookQueueListenerTest.java
1つずつ見ていきます。
build.gradle は testCompile("org.jmockit:jmockit:1.30")
のコメントアウトを一旦元に戻して JMockit が入っている状態にします。
LibraryHelperTest.java を修正する
テストクラスは JMockit を使用して以下のように実装されています。
package ksbysample.webapp.lending.helper.library; import ksbysample.webapp.lending.dao.LibraryForsearchDao; import ksbysample.webapp.lending.entity.LibraryForsearch; import mockit.Delegate; import mockit.Expectations; import mockit.Injectable; import mockit.Tested; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import static org.assertj.core.api.Assertions.assertThat; @RunWith(SpringRunner.class) @SpringBootTest public class LibraryHelperTest { @Tested private LibraryHelper libraryHelper; @Injectable private LibraryForsearchDao libraryForsearchDao; @Test public void testGetSelectedLibrary_図書館が選択されていない場合() throws Exception { new Expectations() {{ libraryForsearchDao.selectSelectedLibrary(); result = null; }}; String result = libraryHelper.getSelectedLibrary(); assertThat(result).isEqualTo("※図書館が選択されていません"); } @Test public void testGetSelectedLibrary_図書館が選択されている場合() throws Exception { new Expectations() {{ libraryForsearchDao.selectSelectedLibrary(); result = new Delegate<LibraryForsearch>() { LibraryForsearch aDelegateMethod() { LibraryForsearch libraryForsearch = new LibraryForsearch(); libraryForsearch.setSystemid("System_Id"); libraryForsearch.setFormal("図書館名"); return libraryForsearch; } }; }}; String result = libraryHelper.getSelectedLibrary(); assertThat(result).isEqualTo("選択中:図書館名"); } }
これを @MockBean アノテーションを使用して以下のように変更します。
package ksbysample.webapp.lending.helper.library; import ksbysample.webapp.lending.dao.LibraryForsearchDao; import ksbysample.webapp.lending.entity.LibraryForsearch; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.context.junit4.SpringRunner; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; @RunWith(SpringRunner.class) @SpringBootTest public class LibraryHelperTest { @Autowired private LibraryHelper libraryHelper; @MockBean private LibraryForsearchDao libraryForsearchDao; @Test public void testGetSelectedLibrary_図書館が選択されていない場合() throws Exception { given(libraryForsearchDao.selectSelectedLibrary()).willReturn(null); String result = libraryHelper.getSelectedLibrary(); assertThat(result).isEqualTo("※図書館が選択されていません"); } @Test public void testGetSelectedLibrary_図書館が選択されている場合() throws Exception { LibraryForsearch libraryForsearch = new LibraryForsearch(); libraryForsearch.setSystemid("System_Id"); libraryForsearch.setFormal("図書館名"); given(libraryForsearchDao.selectSelectedLibrary()).willReturn(libraryForsearch); String result = libraryHelper.getSelectedLibrary(); assertThat(result).isEqualTo("選択中:図書館名"); } }
private LibraryHelper libraryHelper;
のアノテーションを@Tested
→@Autowired
へ変更します。private LibraryForsearchDao libraryForsearchDao;
のアノテーションを@Injectable
→@MockBean
へ変更します。testGetSelectedLibrary_図書館が選択されていない場合()
テストメソッド内のモック化の処理をnew Expectations() {{ ... }};
→given(libraryForsearchDao.selectSelectedLibrary()).willReturn(null);
へ変更します。testGetSelectedLibrary_図書館が選択されている場合()
テストメソッド内のモック化の処理をnew Expectations() {{ ... }};
→given(libraryForsearchDao.selectSelectedLibrary()).willReturn(libraryForsearch);
へ変更します。
LibraryHelperTest クラスのテストのみ実行してみると、全てのテストが成功しました。
BooklistServiceTest.java を修正する
テストクラスは JMockit を使用して以下のように実装されています。
package ksbysample.webapp.lending.web.booklist; import ksbysample.common.test.rule.db.TableDataAssert; import ksbysample.common.test.rule.db.TestDataResource; import ksbysample.webapp.lending.entity.LendingBook; import ksbysample.webapp.lending.security.LendingUserDetailsHelper; import ksbysample.webapp.lending.service.file.BooklistCsvFileServiceTest; import mockit.Expectations; import org.dbunit.dataset.IDataSet; import org.dbunit.dataset.csv.CsvDataSet; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import javax.sql.DataSource; import java.io.File; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; @RunWith(SpringRunner.class) @SpringBootTest public class BooklistServiceTest { private static final String MAILADDR_TANAKA_TARO = "tanaka.taro@sample.com"; @Rule @Autowired public TestDataResource testDataResource; @Autowired private DataSource dataSource; @Autowired private BooklistService booklistService; @Test public void testTemporarySaveBookListCsvFile() throws Exception { new Expectations(LendingUserDetailsHelper.class) {{ LendingUserDetailsHelper.getLoginUserId(); result = Long.valueOf(1L); }}; UploadBooklistForm uploadBooklistForm = new UploadBooklistForm(); // テスト用のユーティリティクラスを作るべきですが、今回は他のテストクラスのメソッドをそのまま使います BooklistCsvFileServiceTest booklistCsvFileServiceTest = new BooklistCsvFileServiceTest(); uploadBooklistForm.setFileupload(booklistCsvFileServiceTest.createNoErrorCsvFile()); Long lendingAppId = booklistService.temporarySaveBookListCsvFile(uploadBooklistForm); assertThat(lendingAppId).isNotEqualTo(Long.valueOf(0L)); IDataSet dataSet = new CsvDataSet( new File("src/test/resources/ksbysample/webapp/lending/web/booklist/assertdata/001")); TableDataAssert tableDataAssert = new TableDataAssert(dataSet, dataSource); tableDataAssert.assertEquals("lending_app" , new String[]{"lending_app_id", "approval_user_id", "version"}); tableDataAssert.assertEquals("lending_book" , new String[]{"lending_book_id", "lending_app_id", "lending_state", "lending_app_flg" , "lending_app_reason", "approval_result", "approval_reason", "version"}); List<LendingBook> lendingBookList = booklistService.getLendingBookList(lendingAppId); assertThat(lendingBookList).hasSize(5); } @Test public void testSendMessageToInquiringStatusOfBookQueue() throws Exception { // 現在の実装では InquiringStatusOfBookQueueServiceTest が通ればOKなので、こちらは実装しない } }
よく見たらモックにしている LendingUserDetailsHelper.getLoginUserId();
は static メソッドでした。調べてみると Mockito では static メソッドはモックにできないので、対応するとすれば以下のいずれかの方法になるようです。
- PowerMock を使用する。
LendingUserDetailsHelper
に @Component アノテーションを付加して Bean にし、LendingUserDetailsHelper#getLoginUserId メソッドから static を取り除く。
Helper クラスなのに static メソッドにしていたのか。。。ということに気付いたので、出来れば後者の方法にしてしまいたいところですが、PowerMock を使ってみたいので、前者の方法で対応してみます。
まず build.gradle をリンク先のその2内容 に変更し、変更後、Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。
BooklistServiceTest.java を以下のように変更します。
@RunWith(PowerMockRunner.class) @PowerMockRunnerDelegate(SpringRunner.class) @SpringBootTest @PrepareForTest(LendingUserDetailsHelper.class) public class BooklistServiceTest { .......... @Test public void testTemporarySaveBookListCsvFile() throws Exception { PowerMockito.mockStatic(LendingUserDetailsHelper.class); given(LendingUserDetailsHelper.getLoginUserId()).willReturn(1L); UploadBooklistForm uploadBooklistForm = new UploadBooklistForm();
@RunWith(SpringRunner.class)
→@RunWith(PowerMockRunner.class)
+@PowerMockRunnerDelegate(SpringRunner.class)
へ変更します。@PrepareForTest(LendingUserDetailsHelper.class)
を class に付加します。- testTemporarySaveBookListCsvFile メソッド内で
new Expectations(LendingUserDetailsHelper.class) {{ ... }};
→PowerMockito.mockStatic(LendingUserDetailsHelper.class);
+given(LendingUserDetailsHelper.getLoginUserId()).willReturn(1L);
へ変更します。
BooklistServiceTest クラスのテストのみ実行してみると、java.lang.IllegalStateException: Failed to load ApplicationContext
のエラーメッセージが表示されてテストが失敗しました。。。
Web で調べたり、コードをいろいろ変えて試してみましたが、テストが成功しません。stackoverflow を見ると上記のアノテーションの組み合わせで PowerMock が使えている人もいるようですが、正直原因が全然分かりません。。。 一旦元に戻して(build.gradle の powermock の記述も削除します)、次のファイルを見ることにします。
InquiringStatusOfBookQueueListenerTest.java を修正する
テストクラスは JMockit を使用して以下のように実装されています。
package ksbysample.webapp.lending.listener.rabbitmq; import com.google.common.base.Charsets; import ksbysample.common.test.rule.db.TableDataAssert; import ksbysample.common.test.rule.db.TestData; import ksbysample.common.test.rule.db.TestDataResource; import ksbysample.common.test.rule.mail.MailServerResource; import ksbysample.webapp.lending.dao.LibraryForsearchDao; import ksbysample.webapp.lending.entity.LibraryForsearch; import ksbysample.webapp.lending.service.calilapi.CalilApiService; import ksbysample.webapp.lending.service.calilapi.response.Book; import ksbysample.webapp.lending.service.calilapi.response.Libkey; import ksbysample.webapp.lending.service.calilapi.response.SystemData; import ksbysample.webapp.lending.service.queue.InquiringStatusOfBookQueueMessage; import mockit.Mock; import mockit.MockUp; import org.dbunit.dataset.IDataSet; import org.dbunit.dataset.csv.CsvDataSet; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.amqp.core.Message; import org.springframework.amqp.core.MessageProperties; import org.springframework.amqp.support.converter.MessageConverter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import javax.mail.internet.MimeMessage; import javax.sql.DataSource; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import static mockit.Deencapsulation.getField; import static mockit.Deencapsulation.setField; import static org.assertj.core.api.Assertions.assertThat; @RunWith(SpringRunner.class) @SpringBootTest public class InquiringStatusOfBookQueueListenerTest { @Rule @Autowired public TestDataResource testDataResource; @Rule @Autowired public MailServerResource mailServer; @Autowired private DataSource dataSource; @Autowired private MessageConverter messageConverter; @Autowired private InquiringStatusOfBookQueueListener listener; @Test @TestData("listener/rabbitmq/testdata/001") public void testReceiveMessage() throws Exception { // モックに入れ替える前のフィールドの実体を退避する LibraryForsearchDao libraryForsearchDaoOrg = getField(listener, "libraryForsearchDao"); CalilApiService calilApiServiceOrg = getField(listener, "calilApiService"); try { /** * モック定義部 */ // InquiringStatusOfBookQueueListener.libraryForsearchDao をモックに入れ替える LibraryForsearchDao libraryForsearchDao = new MockUp<LibraryForsearchDao>() { @Mock LibraryForsearch selectSelectedLibrary() { LibraryForsearch libraryForsearch = new LibraryForsearch(); libraryForsearch.setSystemid("System_Id"); libraryForsearch.setFormal("図書館名"); return libraryForsearch; } }.getMockInstance(); setField(listener, "libraryForsearchDao", libraryForsearchDao); // InquiringStatusOfBookQueueListener.calilApiService をモックに入れ替える CalilApiService calilApiService = new MockUp<CalilApiService>() { @Mock public List<Book> check(String systemid, List<String> isbnList) { List<Book> bookList = new ArrayList<>(); bookList.add(new Book("978-4-7741-6366-6", null, new SystemData(null, null, null, Arrays.asList(new Libkey(null, "貸出可"))))); bookList.add(new Book("978-4-7741-5377-3", null, new SystemData(null, null, null, Arrays.asList(new Libkey(null, "蔵書あり"))))); bookList.add(new Book("978-4-7973-8014-9", null, new SystemData(null, null, null, Arrays.asList(new Libkey(null, "貸出中"))))); bookList.add(new Book("978-4-7973-4778-4", null, new SystemData(null, null, null, Arrays.asList(new Libkey(null, "準備中"))))); bookList.add(new Book("978-4-87311-704-1", null, new SystemData(null, null, null, Arrays.asList(new Libkey(null, "蔵書なし"))))); return bookList; } }.getMockInstance(); setField(listener, "calilApiService", calilApiService); /** * テスト本体 */ InquiringStatusOfBookQueueMessage queueMessage = new InquiringStatusOfBookQueueMessage(); queueMessage.setLendingAppId(1L); Message message = messageConverter.toMessage(queueMessage, new MessageProperties()); listener.receiveMessage(message); /** * 検証 */ // テーブルの以下のカラムのデータを検証する // ・lending_app.status // ・lending_book.lending_state IDataSet dataSet = new CsvDataSet(new File("src/test/resources/ksbysample/webapp/lending/listener/rabbitmq/assertdata/001")); TableDataAssert tableDataAssert = new TableDataAssert(dataSet, dataSource); tableDataAssert.assertEquals("lending_app", new String[]{"lending_app_id", "lending_user_id", "approval_user_id", "version"}); tableDataAssert.assertEquals("lending_book", new String[]{"lending_book_id", "lending_app_id", "isbn", "book_name", "lending_app_flg", "lending_app_reason", "approval_result", "approval_reason", "version"}); // 送信されたメールを検証する assertThat(mailServer.getMessagesCount()).isEqualTo(1); MimeMessage mimeMessage = mailServer.getFirstMessage(); assertThat(mimeMessage.getRecipients(javax.mail.Message.RecipientType.TO)) .extracting(Object::toString) .containsOnly("tanaka.taro@sample.com"); assertThat(mimeMessage.getContent()) .isEqualTo(com.google.common.io.Files.toString( new File("src/test/resources/ksbysample/webapp/lending/helper/mail/assertdata/001/message.txt") , Charsets.UTF_8)); } finally { // モックに差し替えたフィールドを退避しておいた元の実体に戻す setField(listener, "libraryForsearchDao", libraryForsearchDaoOrg); setField(listener, "calilApiService", calilApiServiceOrg); } } }
これを @MockBean アノテーションを使用して以下のように変更します。
@RunWith(SpringRunner.class) @SpringBootTest public class InquiringStatusOfBookQueueListenerTest { .......... @Autowired private InquiringStatusOfBookQueueListener listener; @MockBean private LibraryForsearchDao libraryForsearchDao; @MockBean private CalilApiService calilApiService; @Test @TestData("listener/rabbitmq/testdata/001") public void testReceiveMessage() throws Exception { /** * モック定義部 */ LibraryForsearch libraryForsearch = new LibraryForsearch(); libraryForsearch.setSystemid("System_Id"); libraryForsearch.setFormal("図書館名"); given(libraryForsearchDao.selectSelectedLibrary()).willReturn(libraryForsearch); List<Book> bookList = new ArrayList<>(); bookList.add(new Book("978-4-7741-6366-6", null, new SystemData(null, null, null, Arrays.asList(new Libkey(null, "貸出可"))))); bookList.add(new Book("978-4-7741-5377-3", null, new SystemData(null, null, null, Arrays.asList(new Libkey(null, "蔵書あり"))))); bookList.add(new Book("978-4-7973-8014-9", null, new SystemData(null, null, null, Arrays.asList(new Libkey(null, "貸出中"))))); bookList.add(new Book("978-4-7973-4778-4", null, new SystemData(null, null, null, Arrays.asList(new Libkey(null, "準備中"))))); bookList.add(new Book("978-4-87311-704-1", null, new SystemData(null, null, null, Arrays.asList(new Libkey(null, "蔵書なし"))))); given(calilApiService.check(any(), any())).willReturn(bookList); /** * テスト本体 */ InquiringStatusOfBookQueueMessage queueMessage = new InquiringStatusOfBookQueueMessage(); queueMessage.setLendingAppId(1L); Message message = messageConverter.toMessage(queueMessage, new MessageProperties()); listener.receiveMessage(message); .......... } }
@MockBean private LibraryForsearchDao libraryForsearchDao;
を追加します。@MockBean private CalilApiService calilApiService;
を追加します。// モックに入れ替える前のフィールドの実体を退避する ... try {
の部分と} finally { ... }
の部分を削除します。- libraryForsearch のデータ生成処理 +
given(libraryForsearchDao.selectSelectedLibrary()).willReturn(libraryForsearch);
を追加します。 - bookList のデータ生成処理 +
given(calilApiService.check(any(), any())).willReturn(bookList);
を追加します。
InquiringStatusOfBookQueueListenerTest クラスのテストのみ実行してみると、全てのテストが成功しました。
全てのテストを実行してみる
BooklistServiceTest.java はまだ JMockit を使用したままなので build.gradle から JMockit は外しません。
今の状態で全てのテストを通しで実行して成功するか確認します。まずは clean タスク → Rebuild Project → build タスクを実行してみます。
“BUILD SUCCESSFUL” のメッセージが表示されました。問題ないようです。
今度は Project Tool Window の src/test から「Run ‘All Tests’ with Coverage」を実行してみます。
こちらも全てのテストが成功しました。
感想&次回は。。。
@MockBean
がすごく使いやすいです。前にモックを作った時はいろいろできる JMockit がいいかな、と思いましたが、この使い勝手なら @MockBean
+ Mockito に切り替え確定です。Spring Boot 1.4 のテストアノテーションの変更ですが、本当にテストがやりやすくなっていると思います。
PowerMock を使う方法が分からなかったので static メソッドをモック化したい場合が少し不安ですが、static メソッドをモック化しないとテストできないのはそもそも作りがおかしいのでは?という気がしています。
次回は、LendingUserDetailsHelper に @Component アノテーションを付加して Bean にし、static メソッドを止めます。その後で今回修正できなかった BooklistServiceTest.java を修正します。
ソースコード
build.gradle
■その1
dependencies { .......... // dependency-management-plugin によりバージョン番号が自動で設定されるもの // Appendix A. Dependency versions ( http://docs.spring.io/platform/docs/current/reference/htmlsingle/#appendix-dependency-versions ) 参照 .......... testCompile("org.yaml:snakeyaml") testCompile("org.mockito:mockito-core") // dependency-management-plugin によりバージョン番号が自動で設定されないもの、あるいは最新バージョンを指定したいもの .......... testCompile("com.jayway.jsonpath:json-path:2.2.0") // testCompile("org.jmockit:jmockit:1.30") testCompile("org.spockframework:spock-core:${spockVersion}") { exclude module: "groovy-all" } ..........
testCompile("org.mockito:mockito-core")
を追加します。testCompile("org.jmockit:jmockit:1.30")
をコメントアウトします。
■その2
dependencies { .......... def powermockitoVersion = "1.6.6" .......... // dependency-management-plugin によりバージョン番号が自動で設定されないもの、あるいは最新バージョンを指定したいもの .......... testCompile("com.google.code.findbugs:jsr305:3.0.1") testCompile("org.powermock:powermock-module-junit4:${powermockitoVersion}") testCompile("org.powermock:powermock-api-mockito:${powermockitoVersion}")
def powermockitoVersion = "1.6.6"
を追加します。testCompile("org.powermock:powermock-module-junit4:${powermockitoVersion}")
を追加します。testCompile("org.powermock:powermock-api-mockito:${powermockitoVersion}")
を追加します。
履歴
2017/04/02
初版発行。
IntelliJ IDEA を 2016.3.5 → 2017.1 へ、Git for Windows を 2.12.0 → 2.12.2 へバージョンアップ
IntelliJ IDEA を 2016.3.5 → 2017.1 へバージョンアップする
IntelliJ IDEA の 2017.1 がリリースされたのでバージョンアップします。
※ksbysample-webapp-lending プロジェクトを開いた状態でバージョンアップしています。
IntelliJ IDEA のメインメニューから「Help」-「Check for Updates…」を選択します。
「Platform and Plugin Updates」ダイアログが表示されます。今回は左下に「Update and Restart」ボタンが表示されていませんので、「Download」ボタンをクリックします。
ブラウザが起動して Download IntelliJ IDEA ページが表示されますので ideaIU-2017.1.exe をダウンロードします。
起動している IntelliJ IDEA を終了します。
ideaIU-2017.1.exe を実行します。
「IntelliJ IDEA Setup」ダイアログが表示されます。「Next >」ボタンをクリックします。
「Uninstall old versions」画面が表示されます。以下の画面のように画面上のチェックボックスを全てチェックした後、「Next >」ボタンをクリックします。
「Choose Install Location」画面が表示されます。「Destination Folder」を C:\IntelliJ_IDEA\2017.1 へ変更した後、「Next >」ボタンをクリックします。
「Installation Options」画面が表示されます。何もチェックせずに「Next >」ボタンをクリックします。
「Choose Start Menu Folder」画面が表示されます。何も変更せずに「Install」ボタンをクリックします。
「Installing」画面が表示され、インストールが行われます。
インストールが完了すると「Completing the IntelliJ IDEA Setup Wizard」画面が表示されます。「Finish」ボタンをクリックします。
C:\IntelliJ_IDEA\2016.3
フォルダが残っているので削除します。C:\IntelliJ_IDEA\2017.1\bin\idea64.exe
を実行します。「Complete Installatioin」ダイアログが表示されます。表示された設定はそのままで「OK」ボタンをクリックします。
IntelliJ IDEA が起動すると画面下部に「Indexing…」のメッセージが表示されますので、終了するまで待機します。
Gradle projects View のツリーを見ると「Tasks」の下に「other」しかない状態になっているので、左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。
「other」以外に「build」等も表示された状態に戻ります。
C:\Users\<ユーザ名>\.IntelliJIdea2016.3
が残っているので削除します。Plugin にアップデートがないか確認します。IntelliJ IDEA のメインメニューから「Help」-「Check for Updates…」を選択します。
今回はアップデートされた Plugin はありませんでした。
clean タスク実行 → Rebuild Project 実行をした後、build タスクを実行して “BUILD SUCCESSFUL” のメッセージが表示されることを確認します。
Project View で src/test を選択した後、コンテキストメニューを表示して「Run ‘All Tests’ with Coverage」を選択し、テストが全て成功することを確認します。
Git for Windows を 2.12.0 → 2.12.2 へバージョンアップする
Git for Windows の 2.12.2 がリリースされていたのでバージョンアップします。
https://git-for-windows.github.io/ の「Download」ボタンをクリックして Git-2.12.2-64-bit.exe をダウンロードします。
Git-2.12.2-64-bit.exe を実行します。
「Git 2.12.2 Setup」ダイアログが表示されます。[Next >]ボタンをクリックします。
「Select Components」画面が表示されます。全てのチェックが外れたままであることを確認した後、[Next >]ボタンをクリックします。
「Adjusting your PATH environment」画面が表示されます。中央の「Use Git from the Windows Command Prompt」が選択されていることを確認後、[Next >]ボタンをクリックします。
「Choosing HTTPS transport backend」画面が表示されます。「Use the OpenSSL library」が選択されていることを確認後、[Next >]ボタンをクリックします。
「Configuring the line ending conversions」画面が表示されます。「Checkout Windows-style, commit Unix-style line endings」が選択されていることを確認した後、[Next >]ボタンをクリックします。
「Configuring the terminal emulator to use with Git Bash」画面が表示されます。「Use Windows'default console window」が選択されていることを確認した後、[Next >]ボタンをクリックします。
「Configuring extra options」画面が表示されます。「Enable file system caching」だけがチェックされていることを確認した後、[Install]ボタンをクリックします。
インストールが完了すると「Completing the Git Setup Wizard」のメッセージが表示された画面が表示されます。中央の「View ReleaseNotes.html」のチェックを外した後、「Finish」ボタンをクリックしてインストーラーを終了します。
コマンドプロンプトを起動して git のバージョンが
git version 2.12.2.windows.1
になっていることを確認します。git-cmd.exe を起動して日本語の表示・入力が問題ないかを確認します。
特に問題はないようですので、2.12.2 で作業を進めたいと思います。
Spring Boot 1.3.x の Web アプリを 1.4.x へバージョンアップする ( その15 )( テストクラスのアノテーションを 1.4 のものに変更する )
概要
記事一覧はこちらです。
- 今回の手順で確認できるのは以下の内容です。
- テストクラスのアノテーションを 1.4 のものに変更します。
参照したサイト・書籍
Spring Boot 1.4.0 Release Notes - Test utilities and classes
https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-1.4-Release-Notes#test-utilities-and-classesSpring Boot Reference Guide - 40. Testing
http://docs.spring.io/spring-boot/docs/1.4.5.RELEASE/reference/htmlsingle/#boot-features-testing
目次
- どう書き換えればよいのか?
- 変更する方針を決める
- 全てのテストを
@RunWith(SpringRunner.class)
+@SpringBootTest
に変更してみる - Controller, RestController 以外のテストを
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
に変更してみる - 次回は。。。
手順
どう書き換えればよいのか?
最低限の変更だけ行うのであれば、以下のように書き換えれば動きます。
@RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = Application.class) @WebAppConfiguration public class LendingapprovalFormValidatorTest {
↓↓↓
@RunWith(SpringRunner.class) @SpringBootTest public class LendingapprovalFormValidatorTest {
@RunWith(SpringJUnit4ClassRunner.class)
→@RunWith(SpringRunner.class)
へ変更します。@SpringBootTest(classes = Application.class)
,@WebAppConfiguration
→@SpringBootTest
へ変更します。
@RunWith(SpringRunner.class)
の方は単純な書き換えなので迷わないのですが、@SpringBootTest
の方は webEnvironment 属性で NONE, MOCK, RANDOM_PORT, DEFINED_PORT のどれを指定すればよいのかが最初よく分かりませんでした。結論としては @SpringBootTest
だけ書けば問題なくテストは動きます(この時はデフォルト値である MOCK が指定された状態です)。
1.3 のアノテーションの時と MOCK, RANDOM_PORT, DEFINED_PORT それぞれを指定した時のテストの実行速度がどれくらい違うのか気になったので src/test/java/ksbysample/webapp/lending/web/lendingapproval/LendingapprovalControllerTest.java で試してみます。NONE はエラーになるので最初から除外します。
テストは LendingapprovalControllerTest.java のクラス名の左側のアイコンをクリックして表示される「Run ‘LendingapprovalControllerTest'」を選択して実行します。
計測時間は IntelliJ IDEA の以下の場所に表示されるものを使用します。
1回目 | 2回目 | 3回目 | 4回目 | 5回目 | 平均 | |
---|---|---|---|---|---|---|
1.3 | 5s 791ms | 6s 128ms | 5s 720ms | 5s 609ms | 6s 56ms | 5s 961ms |
MOCK | 5s 906ms | 6s 43ms | 5s 979ms | 6s 225ms | 5s 910ms | 6s 9ms |
RANDOM_PORT | 5s 657ms | 5s 766ms | 5s 555ms | 5s 207ms | 5s 388ms | 5s 514ms |
DEFINED_PORT | 6s 702ms | 5s 542ms | 5s 694ms | 5s 632ms | 5s 787ms | 5s 871ms |
MOCK で遅いの?と思ったので、この後3回程試してみたところ 5s 625ms、6s 8ms、5s 274ms でした。最後の1回だけはそこそこ速いです。MOCK, RANDOM_PORT, DEFINED_PORT でたいした差はないのではないか?という気がしますが、今ひとつはっきりしませんね。。。
もう1つ思ったのは、Controller のテストでなければ @SpringBootTest
ではなく @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
を指定した方がテストが速いのではないか?という点です。
src/test/java/ksbysample/webapp/lending/web/lendingapproval/LendingapprovalFormValidatorTest.java で試してみます。
1回目 | 2回目 | 3回目 | 4回目 | 5回目 | 平均 | |
---|---|---|---|---|---|---|
1.3 | 215ms | 167ms | 254ms | 207ms | 180ms | 204ms |
NONE | 190ms | 291ms | 180ms | 182ms | 199ms | 208ms |
MOCK | 208ms | 197ms | 201ms | 211ms | 196ms | 202ms |
NONE は2回目が遅かった影響で平均が他と大差なくなっていますが、MOCK と比較すると 10~20ms 程度速くなるような気がします。
変更する方針を決める
まずはシンプルに一律以下のアノテーションに変更してテストが全て成功することを確認してみます。MOCK, RANDOM_PORT, DEFINED_PORT で差がない気がするので、シンプルに @SpringBootTest
だけ書くことにします。
@RunWith(SpringRunner.class) @SpringBootTest public class LendingapprovalFormValidatorTest {
その後で Controller, RestController 以外のテストを @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
に変えて、テストの総実行時間が速くなるのか確認してみたいと思います。
また、Controller, RestController のテストを Controller の機能だけのテストにしていれば @WebMvcTest
アノテーションを使用するのもありだな、と思いましたが、作成してあるテストは Service まで含めたものになっているので、今回 @WebMvcTest
は使いません。Controller は Mock を利用して Controller だけのテストをした方がよいのかもしれません。
全てのテストを @RunWith(SpringRunner.class)
+ @SpringBootTest
に変更してみる
以下の手順でアノテーションを変更します。
- Ctrl+Alt+F で「Find in Path」ダイアログを表示した後、
@RunWith(SpringJUnit4ClassRunner.class)
で project 全体を検索します。 - ヒットした箇所を
@RunWith(SpringRunner.class)
+@SpringBootTest
の組み合わせに変更した後、Ctrl+Alt+o を押して不要な import 文を削除します。
また Spock で書いてあるテストについては以下のように変更します。
@ContextConfiguration(loader = SpringApplicationContextLoader.class, classes = Application.class) @WebAppConfiguration class ValuesHelperTest extends Specification {
↓↓↓
@SpringBootTest class ValuesHelperTest extends Specification {
全てのテストを変更したら clean タスク → Rebuild Project → build タスクを実行してみます。
build タスクは “BUILD SUCCESSFUL” が表示されて成功しました。
Project Tool Window の src/test から「Run ‘All Tests’ with Coverage」を実行してみます。
こちらも全てのテストが成功しました。テストの実行速度も体感的には以前と変わりません、というより少し速くなっているような気がします。
5回計測してみます。
1回目 | 2回目 | 3回目 | 4回目 | 5回目 | 平均 |
---|---|---|---|---|---|
29s 709ms | 29s 431ms | 28s 175ms | 29s 293ms | 29s 344ms | 29s 190ms |
IntelliJ IDEA を 2016.3.4 → 2016.3.5 へ、Git for Windows を 2.11.1 → 2.12.0 へバージョンアップ の記事だと 1m 3s 669ms、Spring Boot 1.3.x の Web アプリを 1.4.x へバージョンアップする ( その13 )( RestTemplate で WebAPI を呼び出している処理に spring-retry でリトライ処理を入れる ) の記事だと 49s 111ms でしたので、やはり速くなっているようです。
Controller, RestController 以外のテストを @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
に変更してみる
以下の手順でアノテーションを変更します。
- Ctrl+Alt+F で「Find in Path」ダイアログを表示した後、
@SpringBootTest
で project 全体を検索します。 - ヒットした箇所のうち、Controller, RestController のテスト以外を
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
に変更します。
全てのテストを変更したら clean タスク → Rebuild Project → build タスクを実行してみます。
build タスクは “BUILD SUCCESSFUL” が表示されて成功しました。
Project Tool Window の src/test から「Run ‘All Tests’ with Coverage」を実行してみると、こちらも全てのテストが成功しました。
5回計測してみます。
1回目 | 2回目 | 3回目 | 4回目 | 5回目 | 平均 |
---|---|---|---|---|---|
29s 314ms | 31s 566ms | 30s 978ms | 30s 473ms | 29s 504ms | 30s 367ms |
少しですが遅くなるようです。MOCK と NONE が混在しているからでしょうか。。。? こちらの変更は元に戻して @SpringBootTest
のみにします。
最初テストクラスのアノテーションはなんか複雑になったような気がしていましたが、@RunWith(SpringRunner.class)
+ @SpringBootTest
を書けば十分でした。実はシンプルになったんですね。
次回は。。。
モックは JMockit で作成していますが、Spring Boot 1.4 から @MockBean アノテーションが提供されて Mockito と組み合わせてモックを作ることが推奨されているようなので、@MockBean + Mockito で作り直してみます。
ソースコード
履歴
2017/03/27
初版発行。
Spring Boot 1.3.x の Web アプリを 1.4.x へバージョンアップする ( その14 )( spring-boot-gradle-plugin は dependency-management-plugin を自動的に適用するので build.gradle に記述する必要がありませんでした )
概要
記事一覧はこちらです。
- 今回の手順で確認できるのは以下の内容です。
- 今年に入ってから dependency-management-plugin の 1.0.0.RELEASE や 1.0.1.RELEASE がリリースされていることに気付いたので適用しようとしていたのですが、なぜかエラーが出て適用できません。
- 調査のために Web でいろいろ調べていたのですが、Spring Boot Reference Guide の 67. Spring Boot Gradle plugin に
The spring-boot plugin automatically applies the Dependency Management Plugin
の文章を見つけました。 - spring-boot-gradle-plugin も Appendix F. Dependency versions に書かれているモジュールについて自動的に依存性解決してくれることは知っていましたが、以前書いた Spring Boot で書籍の貸出状況確認・貸出申請する Web アプリケーションを作る ( 番外編 )( IntelliJ IDEA Ultimate Edition での Spring Boot プロジェクトの作成 ) の記事で、自動生成された build.gradle に
classpath("io.spring.gradle:dependency-management-plugin:0.5.1.RELEASE")
の記述があったため、入れるものだと思い続けていたようです。 The spring-boot plugin automatically applies the Dependency Management Plugin
の記述通りなら build.gradle に dependency-management-plugin を書く必要はないと思われるので、確認してみます。
参照したサイト・書籍
目次
- まずは単に
classpath("io.spring.gradle:dependency-management-plugin:0.6.1.RELEASE")
をコメントアウトしてみる dependencyManagement { ... }
もコメントアウトするとどうなるのか?- Spring IO Platform の POM を記述した場合と記述しない場合で何が違うのか?
- build.gradle を修正する
- 次回は。。。
手順
まずは単に classpath("io.spring.gradle:dependency-management-plugin:0.6.1.RELEASE")
をコメントアウトしてみる
build.gradle から dependency-management-plugin に関する部分をコメントアウトしてみます。
buildscript { ext { springBootVersion = '1.4.4.RELEASE' } repositories { jcenter() maven { url "http://repo.spring.io/repo/" } maven { url "https://plugins.gradle.org/m2/" } } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") // classpath("io.spring.gradle:dependency-management-plugin:0.6.1.RELEASE") // for Error Prone ( http://errorprone.info/ ) classpath("net.ltgt.gradle:gradle-errorprone-plugin:0.0.9") // for Grgit classpath("org.ajoberstar:grgit:1.8.0") // Gradle Download Task classpath("de.undercouch:gradle-download-task:3.2.0") } } apply plugin: 'java' apply plugin: 'eclipse' apply plugin: 'idea' apply plugin: 'org.springframework.boot' //apply plugin: 'io.spring.dependency-management' apply plugin: 'de.undercouch.download' apply plugin: 'groovy' apply plugin: 'net.ltgt.errorprone' apply plugin: 'checkstyle' apply plugin: 'findbugs'
- 以下の記述の行をコメントアウトします。
classpath("io.spring.gradle:dependency-management-plugin:0.6.1.RELEASE")
apply plugin: 'io.spring.dependency-management'
コメントアウト後、Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新すると、エラーは出ずに終了しました。
clean タスク → Rebuild Project → build タスク も実行してみましたが、こちらも無事 “BUILD SUCCESSFUL” が表示されました。
gradlew dependencies
コマンドを実行してみると checkstyle が依存する guava のバージョンが 21.0 になっています。Spring Boot 1.4.5 の Appendix F. Dependency versions には com.google.guava:guava
の記述はないので、build.gradle に記述している dependencyManagement { imports { mavenBom("io.spring.platform:platform-bom:Athens-SR3") { ... } } }
の BOM の内容が反映されていることが確認できます。
checkstyle - The Checkstyle libraries to be used for this project. \--- com.puppycrawl.tools:checkstyle:7.5.1 +--- antlr:antlr:2.7.7 +--- org.antlr:antlr4-runtime:4.6 +--- commons-beanutils:commons-beanutils:1.9.3 | \--- commons-collections:commons-collections:3.2.2 +--- commons-cli:commons-cli:1.3.1 \--- com.google.guava:guava:19.0 -> 21.0
よって、Spring IO Platform の BOM を利用したい場合には spring-boot-gradle-plugin だけ記述すればよく、dependency-management-plugin を記述する必要はありませんでした。
dependencyManagement { ... }
もコメントアウトするとどうなるのか?
更に build.gradle の dependencyManagement { ... }
を削除するとどうなるのか興味が湧いたので、試してみます。
//dependencyManagement { // imports { // mavenBom("io.spring.platform:platform-bom:Athens-SR3") { // bomProperty 'commons-lang3.version', '3.5' // bomProperty 'guava.version', '21.0' // } // } //}
コメントアウト後、Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新すると、以下のエラーが表示されました。
これだけでは原因が分からないので、コマンドラインから gradlew dependencies
コマンドを実行してみると以下のエラーが表示されました。guava と commons-lang3 の依存性解決に失敗したのが原因でした。
Spring Boot 1.4.5 の Appendix F. Dependency versions には guava も commons-lang3 も記述されていないので当然と言えば当然ですね。
build.gradle の dependencies 内で guava と commons-lang3 にバージョン番号を明記してみます。
compile("com.google.guava:guava:21.0") compile("org.apache.commons:commons-lang3:3.5")
変更後、Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新すると、エラーは出ずに終了しました。
clean タスク → Rebuild Project → build タスク を実行すると、無事 “BUILD SUCCESSFUL” が表示されました。ただしなんか足りないライブラリをダウンロードしています。
gradlew dependencies
コマンドを実行してみると checkstyle が依存する Guava のバージョンは 19.0 のままでした。
checkstyle - The Checkstyle libraries to be used for this project. \--- com.puppycrawl.tools:checkstyle:7.5.1 +--- antlr:antlr:2.7.7 +--- org.antlr:antlr4-runtime:4.6 +--- commons-beanutils:commons-beanutils:1.9.3 | \--- commons-collections:commons-collections:3.2.2 +--- commons-cli:commons-cli:1.3.1 \--- com.google.guava:guava:19.0
Spring IO Platform の POM を記述した場合と記述しない場合で何が違うのか?
両方の場合の gradlew dependencies
コマンドの出力結果を比較してみました。
■checkstyle - The Checkstyle libraries to be used for this project.
component | 記述した場合 | 記述しない場合 |
---|---|---|
com.google.guava:guava | 21.0 | 19.0 |
■errorprone
component | 記述した場合 | 記述しない場合 |
---|---|---|
com.google.guava:guava | 21.0 | 20.0 |
com.google.code.findbugs:jsr305 | 3.0.1 | 3.0.0 |
■findbugs - The FindBugs libraries to be used for this project.
component | 記述した場合 | 記述しない場合 |
---|---|---|
com.google.code.findbugs:jsr305 | 3.0.1 | 3.0.0 |
■上記以外
component | 記述した場合 | 記述しない場合 |
---|---|---|
javax.activation:activation | 1.1.1 | 1.1 |
com.rabbitmq:amqp-client | 3.6.6 | 3.6.5 |
org.objenesis:objenesis | 2.4 | 2.1 |
guava, commons-lang3 以外にも BOM を記述した方がバージョンが上がるものがあります。
build.gradle を修正する
Spring IO Platform の BOM を記述した方が guava や commons-lang3 も依存性解決の対象になって個人的には好みなので、今回は単に dependency-management-plugin の記述だけを削除することにします。
build.gradle を リンク先の内容 に変更した後、Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。問題が出ないことは上に書きましたので、ここでは改めて記述しません。
次回は。。。
Spring Boot 1.4 で一番変更が入ったと聞いているテストクラスのアノテーションを見直します。
ソースコード
build.gradle
buildscript { ext { springBootVersion = '1.4.4.RELEASE' } repositories { jcenter() maven { url "http://repo.spring.io/repo/" } maven { url "https://plugins.gradle.org/m2/" } } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") // for Error Prone ( http://errorprone.info/ ) classpath("net.ltgt.gradle:gradle-errorprone-plugin:0.0.9") // for Grgit classpath("org.ajoberstar:grgit:1.8.0") // Gradle Download Task classpath("de.undercouch:gradle-download-task:3.2.0") } } apply plugin: 'java' apply plugin: 'eclipse' apply plugin: 'idea' apply plugin: 'org.springframework.boot' apply plugin: 'de.undercouch.download' apply plugin: 'groovy' apply plugin: 'net.ltgt.errorprone' apply plugin: 'checkstyle' apply plugin: 'findbugs'
classpath("io.spring.gradle:dependency-management-plugin:0.6.1.RELEASE")
を削除します。apply plugin: 'io.spring.dependency-management'
を削除します。
履歴
2017/03/24
初版発行。
Spring Boot 1.3.x の Web アプリを 1.4.x へバージョンアップする ( 番外編 )( IntelliJ IDEA に Request mapper Plugin をインストールする )
概要
記事一覧はこちらです。
今朝 Twitter を見ていたら、珍しく IntelliJ IDEA の Plugin に関するメッセージが流れてきました。
Cool, #springmvc controller navigation for @intellijidea https://t.co/bifoFtnmWQ
— SpringCentral (@springcentral) 2017年3月20日
Request mapper という名称の Plugin で、@RequestMapping 系のアノテーションに記述した URL を検索・ジャンプすることができる Plugin のようです。
Spring Tool Window の MVC タブでも URL 一覧は表示できますが、Plugin を紹介している動画を見ていると軽快に検索・ジャンプできるように見えて便利そうだったので、入れて試してみたいと思います。
参照したサイト・書籍
目次
手順
Request mapper Plugin をインストールする
IntelliJ IDEA のメインメニューから「File」-「Settings…」を選択します。
「Settings」ダイアログが表示されます。画面左側のリストから「Plugins」を選択した後、画面中央下にある「Browse repositories…」ボタンをクリックします。
「Browse Repositories」ダイアログが表示されます。左上の検索フィールドに “request mapper” と入力すると、その下のリストに Request mapper Plugin が表示されますので、画面右側に表示される「Install」ボタンをクリックします。
Plugin がダウンロードされた後「Install」ボタンが「Restart IntelliJ IDEA」ボタンに変わりますので、クリックします。
一旦「Settings」ダイアログに戻りますので、画面下の「OK」ボタンをクリックします。
「Platform and Plugin Updates」ダイアログが表示されますので「Restart」ボタンをクリックします。
Request mapper Plugin を使ってみる
Ctrl+Shift+¥ を入力すると画面中央に検索フィールドが表示されます。
“/” だけ入力すると全ての URL がヒットするようです。URL 一覧を見てみるとテストクラスに記述した “/exceptionHandlerAdviceTest” もヒットしていました。src/main/java の下だけでなく src/main/test の下も対象になるようです。
中間一致検索もしてくれます。例えば “download” という文字を入力すると、URL に “download” という文字が含まれているものだけが表示されます。
またそのまま続けて “view” と入力すると、"download" と “view” という文字が含まれている URL だけが表示されました。大文字小文字の区別はされていないので、検索しやすそうです。
検索文字列のヒットには、途中に “/” が入っているとかは関係ないようです。"resultresponse" と入力すると “result” と “response” という文字が含まれる URL が表示されました。
ただし “result” + “oad” ( “download” の最後の3文字 ) = “resultoad” と入力すると何もヒットしませんでした。単語を区別して検索しているのでしょうか? URL がヒットしないと検索文字列が赤く表示されます。
単に “oad” だとヒットしますね。
検索文字列を他に試していたところ、"resultfile" と “resultby” はヒットするのに “resultdownload” はヒットしませんでした。最初に文字列をヒットした後は、キャメルケースになっているところでヒットするか否かを判断しているようです。
ヒットした URL 一覧からソースコードにジャンプしたい URL を選択して Enter キーを押すと、対応する箇所にジャンプします。
少し使ってみただけですが、動作が軽快でいいですね! 以前から欲しいと思っていた機能の Plugin なので、しばらく使ってみたいと思います。
履歴
2017/03/22
初版発行。
IntelliJ IDEA を 2016.3.4 → 2016.3.5 へ、Git for Windows を 2.11.1 → 2.12.0 へバージョンアップ
IntelliJ IDEA を 2016.3.4 → 2016.3.5 へバージョンアップする
IntelliJ IDEA の 2016.3.5 がリリースされたのでバージョンアップします。
- IntelliJ IDEA 2016.3.5 Release Notes
https://confluence.jetbrains.com/display/IDEADEV/IntelliJ+IDEA+2016.3.5+Release+Notes
※ksbysample-webapp-lending プロジェクトを開いた状態でバージョンアップしています。
IntelliJ IDEA のメインメニューから「Help」-「Check for Updates…」を選択します。
「Platform and Plugin Updates」ダイアログが表示されます。左下に「Update and Restart」ボタンが表示されていますので、「Update and Restart」ボタンをクリックします。
再び「Platform and Plugin Updates」ダイアログが表示され CheckStyle-IDEA, JRebel for IntelliJ のアップデートも表示されますので、そのまま「Update and Restart」ボタンをクリックします。
Patch と Plugin がダウンロードされて IntelliJ IDEA が再起動します。
メイン画面が表示されると画面下部に「Indexing…」のメッセージが表示されますので、終了するまで待機します。
処理が終了すると Gradle Tool Window のツリーの表示が other グループしかない初期の状態に戻っていますので、左上の「Refresh all Gradle projects」ボタンをクリックして更新します。更新が完了すると build グループ等が表示されます。
clean タスク実行 → Rebuild Project 実行をした後、build タスクを実行して “BUILD SUCCESSFUL” のメッセージが表示されることを確認します。
Project Tool Window で src/test を選択した後、コンテキストメニューを表示して「Run ‘All Tests’ with Coverage」を選択し、テストが全て成功することを確認します。
Git for Windows を 2.11.1 → 2.12.0 へバージョンアップする
Git for Windows の 2.12.0 がリリースされていたのでバージョンアップします。
https://git-for-windows.github.io/ の「Download」ボタンをクリックして Git-2.12.0-64-bit.exe をダウンロードします。
Git-2.12.0-64-bit.exe を実行します。
「Git 2.12.0 Setup」ダイアログが表示されます。[Next >]ボタンをクリックします。
「Select Components」画面が表示されます。全てのチェックが外れたままであることを確認した後、[Next >]ボタンをクリックします。
「Adjusting your PATH environment」画面が表示されます。中央の「Use Git from the Windows Command Prompt」が選択されていることを確認後、[Next >]ボタンをクリックします。
「Configuring the line ending conversions」画面が表示されます。「Checkout Windows-style, commit Unix-style line endings」が選択されていることを確認した後、[Next >]ボタンをクリックします。
「Configuring the terminal emulator to use with Git Bash」画面が表示されます。「Use Windows'default console window」が選択されていることを確認した後、[Next >]ボタンをクリックします。
「Configuring extra options」画面が表示されます。「Enable file system caching」だけがチェックされていることを確認した後、[Install]ボタンをクリックします。
インストールが完了すると「Completing the Git Setup Wizard」のメッセージが表示された画面が表示されます。中央の「View ReleaseNotes.html」のチェックを外した後、「Finish」ボタンをクリックしてインストーラーを終了します。
コマンドプロンプトを起動して git のバージョンが
git version 2.12.0.windows.1
になっていることを確認します。git-cmd.exe を起動して日本語の表示・入力が問題ないかを確認します。
特に問題はないようですので、2.12.0 で作業を進めたいと思います。
Spring Boot 1.3.x の Web アプリを 1.4.x へバージョンアップする ( その13 )( RestTemplate で WebAPI を呼び出している処理に spring-retry でリトライ処理を入れる )
概要
記事一覧はこちらです。
- 今回の手順で確認できるのは以下の内容です。
- 前回の検証で WebAPI を呼び出している処理にリトライ処理を入れればテストが成功することが分かったので、spring-retry でリトライ処理を入れます。
- @Retryable アノテーションではなく RetryTemplate クラスで実装します。
- リトライ処理は、以下のルールにします。
- Exception.class ( あるいはその継承クラス ) が throw されたらリトライします。
- 最大5回リトライします。
- リトライ間隔は5秒固定にします。
- リトライが全てエラーになった場合には、発生した例外がそのまま throw されるようにします。
- spring-retry の処理については以前書いた以下の記事を参考にして実装します。
参照したサイト・書籍
目次
手順
build.gradle を変更する
gradlew dependencies
を実行すると org.springframework.boot:spring-boot-starter-amqp
の依存関係で spring-retry は入っていることが確認できたのですが、
compile - Dependencies for source set 'main'. .......... +--- org.springframework.boot:spring-boot-starter-amqp: -> 1.4.4.RELEASE | +--- org.springframework.boot:spring-boot-starter:1.4.4.RELEASE (*) | +--- org.springframework:spring-messaging:4.3.6.RELEASE | | +--- org.springframework:spring-beans:4.3.6.RELEASE (*) | | +--- org.springframework:spring-context:4.3.6.RELEASE (*) | | \--- org.springframework:spring-core:4.3.6.RELEASE | \--- org.springframework.amqp:spring-rabbit:1.6.7.RELEASE | +--- org.springframework:spring-web:4.2.9.RELEASE -> 4.3.6.RELEASE (*) | +--- org.springframework.retry:spring-retry:1.1.5.RELEASE | | \--- org.springframework:spring-core:4.0.4.RELEASE -> 4.3.6.RELEASE | +--- org.springframework:spring-messaging:4.2.9.RELEASE -> 4.3.6.RELEASE (*) | +--- org.springframework:spring-context:4.2.9.RELEASE -> 4.3.6.RELEASE (*) | +--- org.springframework:spring-tx:4.2.9.RELEASE -> 4.3.6.RELEASE (*) | +--- com.rabbitmq:http-client:1.0.0.RELEASE | | +--- org.apache.httpcomponents:httpclient:4.3.6 -> 4.5.2 | | | +--- org.apache.httpcomponents:httpcore:4.4.4 -> 4.4.6 | | | \--- commons-codec:commons-codec:1.9 -> 1.10 | | \--- com.fasterxml.jackson.core:jackson-databind:2.5.1 -> 2.8.6 (*) | +--- org.springframework.amqp:spring-amqp:1.6.7.RELEASE | | \--- org.springframework:spring-core:4.2.9.RELEASE -> 4.3.6.RELEASE | \--- com.rabbitmq:amqp-client:3.6.5 -> 3.6.6 ..........
明示的に build.gradle に記述することにします。
- build.gradle を リンク先の内容 に変更します。変更後、Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。
spring-retry でリトライ処理を入れる
src/main/java/ksbysample/webapp/lending/config/ApplicationConfig.java を リンク先の内容 に変更します。
src/main/java/ksbysample/webapp/lending/service/calilapi/CalilApiService.java を リンク先の内容 に変更します。
動作確認
clean タスク → Rebuild Project → build タスク を実行して “BUILD SUCCESSFUL” が表示されることを確認します。
Project Tool Window の src/test から「Run ‘All Tests’ with Coverage」を実行して、テストが全て成功することを確認します。
メモ書き
メソッドに @Retryable アノテーションを付けるだけでリトライしてくれる訳ではないらしい。@Retryable アノテーションでリトライさせるための条件を忘れているな。。。
ソースコード
build.gradle
dependencies { .......... // dependency-management-plugin によりバージョン番号が自動で設定されるもの // Appendix A. Dependency versions ( http://docs.spring.io/platform/docs/current/reference/htmlsingle/#appendix-dependency-versions ) 参照 .......... compile("org.springframework.session:spring-session") compile("org.springframework.retry:spring-retry") compile("com.fasterxml.jackson.datatype:jackson-datatype-jsr310") ..........
compile("org.springframework.retry:spring-retry")
を追加します。
ApplicationConfig.java
@Configuration public class ApplicationConfig { .......... @Bean public RetryTemplate simpleRetryTemplate() { RetryTemplate retryTemplate = new RetryTemplate(); retryTemplate.setRetryPolicy( new SimpleRetryPolicy(5, singletonMap(Exception.class, true))); FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy(); fixedBackOffPolicy.setBackOffPeriod(5000); retryTemplate.setBackOffPolicy(fixedBackOffPolicy); return retryTemplate; } }
- simpleRetryTemplate メソッドを追加します。
CalilApiService.java
@Service @PropertySource("classpath:calilapi.properties") public class CalilApiService { .......... private final RestTemplate restTemplateForCalilApi; private final RestTemplate restTemplateForCalilApiByXml; private final RetryTemplate simpleRetryTemplate; public CalilApiService(@Qualifier("restTemplateForCalilApi") RestTemplate restTemplateForCalilApi , @Qualifier("restTemplateForCalilApiByXml") RestTemplate restTemplateForCalilApiByXml , @Qualifier("simpleRetryTemplate") RetryTemplate simpleRetryTemplate) { this.restTemplateForCalilApi = restTemplateForCalilApi; this.restTemplateForCalilApiByXml = restTemplateForCalilApiByXml; this.simpleRetryTemplate = simpleRetryTemplate; } .......... public Libraries getLibraryList(String pref) throws Exception { // 図書館データベースAPIを呼び出して XMLレスポンスを受信する ResponseEntity<String> response = getForEntityWithRetry(this.restTemplateForCalilApi , URL_CALILAPI_LIBRALY, String.class, this.calilApiKey, pref); // 受信した XMLレスポンスを Javaオブジェクトに変換する Serializer serializer = new Persister(); Libraries libraries = serializer.read(Libraries.class, response.getBody()); return libraries; } public LibrariesForJackson2Xml getLibraryListByJackson2Xml(String pref) throws Exception { // 図書館データベースAPIを呼び出して XMLレスポンスを受信する ResponseEntity<LibrariesForJackson2Xml> response = getForEntityWithRetry(this.restTemplateForCalilApiByXml , URL_CALILAPI_LIBRALY, LibrariesForJackson2Xml.class, this.calilApiKey, pref); return response.getBody(); } public List<Book> check(String systemid, List<String> isbnList) { .......... ResponseEntity<CheckApiResponse> response = null; String url = URL_CALILAPI_CHECK; for (int retry = 0; retry < RETRY_MAX_CNT; retry++) { // 蔵書検索APIを呼び出して蔵書の有無と貸出状況を取得する response = getForEntityWithRetry(this.restTemplateForCalilApiByXml, url, CheckApiResponse.class, vars); logger.info("カーリルの蔵書検索API を呼び出し、レスポンスを取得しました。{}", response.getBody().toString()); if (response.getBody().getContinueValue() == 0) { break; } .......... } return response.getBody().getBookList(); } private <T> ResponseEntity<T> getForEntityWithRetry(RestTemplate restTemplate, String url , Class<T> responseType, Object... uriVariables) { ResponseEntity<T> response = this.simpleRetryTemplate.execute(context -> { if (context.getRetryCount() > 0) { logger.info("★★★ リトライ回数 = " + context.getRetryCount()); } ResponseEntity<T> innerResponse = restTemplate.getForEntity(url, responseType, uriVariables); return innerResponse; }); return response; }
private final RetryTemplate simpleRetryTemplate;
を追加し、コンストラクタインジェクションの処理も追加します。- getForEntityWithRetry メソッドを追加します。
- 以下のメソッド内で RestTemplate#getForEntity を呼び出しているところを getForEntityWithRetry メソッドを使用するように変更します。
- getLibraryList メソッド
- getLibraryListByJackson2Xml メソッド
- check メソッド
履歴
2017/03/20
初版発行。
2017/03/22
* @Retryable アノテーションで実装したのですが、例外が throw されてもリトライしないことに気付いたので、RetryTemplate クラスを使用する方法に修正しました。