Spring Boot で書籍の貸出状況確認・貸出申請する Web アプリケーションを作る ( その53 )( 貸出申請結果確認画面の作成5 )
概要
Spring Boot で書籍の貸出状況確認・貸出申請する Web アプリケーションを作る ( その52 )( 貸出申請結果確認画面の作成4 ) の続きです。
- 今回の手順で確認できるのは以下の内容です。
- 貸出申請結果確認画面の作成
- テストの作成
- 貸出申請結果確認画面の作成
参照したサイト・書籍
-
GroovyでJUnitなテストを書くときの注意点……なんて無かった
http://irof.hateblo.jp/entry/20121213/p1- Spock で
@RunWith(Enclosed.class)
を使用してテストを書く方法を調査した時に参照しました。
- Spock で
目次
- テスト作成対象のクラスを決める
- printClassWhatNotMakeTest タスクのチェック対象外のパッケージを設定する
- ConfirmresultController クラスのテストを Spock で書くとどうなるのか?
- ConfirmresultController クラスのテストの作成
- 全てのテストが成功するか確認する
- commit、Push、Pull Request、マージ
- 次回は。。。
- メモ書き
手順
テスト作成対象のクラスを決める
-
Gradle projects View から printClassWhatNotMakeTest タスクを実行します。
なぜかテストクラスを作成したはずの ValuesHelper.java が出力されています?
作成したはずの ValuesHelper.java のテストクラスがなくなっている原因を調査します。Spock で作成したはずなので GitHub で src/test/groovy/ksbysample/webapp/lending の下を見てみると記録されている履歴は2つ。
履歴の中を見てみると「#53 貸出承認画面のテストを作成しました」の方でなぜか削除していました。。。 気付いていませんでした。。。
削除してしまったファイルを元に戻します。コマンドプロンプトを起動して git-cmd.exe を実行した後、以下のコマンドを実行します。
> cd /c/project-springboot/ksbysample-webapp-lending
> git checkout 744e0ae src/test/groovy/ksbysample/webapp/lending/values/ValuesHelperTest.groovy再度 Gradle projects View から printClassWhatNotMakeTest タスクを実行します。今度は ValuesHelper.java は出力されていません。
出力されたクラスに対して以下の対応を行います。
- 以下のクラス、インターフェースは printClassWhatNotMakeTest タスクのチェック対象外にします。
- src/main/java/ksbysample/webapp/lending/helper/download/booklistcsv /BookListCsvData.java
- src/main/java/ksbysample/webapp/lending/helper/download/booklistcsv /BookListCsvDataConverter.java
- src/main/java/ksbysample/webapp/lending/helper/download/booklistcsv /BookListCsvDownloadHelper.java
- src/main/java/ksbysample/webapp/lending/helper/download /DataDownloadHelper.java
- src/main/java/ksbysample/webapp/lending/view/BookListCsvView.java
- 以下のクラスは Junit でテストを作成します。
- 以下のクラス、インターフェースは printClassWhatNotMakeTest タスクのチェック対象外にします。
printClassWhatNotMakeTest タスクのチェック対象外のパッケージを設定する
- build.gradle を リンク先の内容 に変更します。
ConfirmresultController クラスのテストを Spock で書くとどうなるのか?
なんか動かない気がしていたので Controller クラスのテストは JUnit で作成していたのですが、書いて試したことはなかったので試してみたいと思います。
src/main/java/ksbysample/webapp/lending/web/confirmresult の下の ConfirmresultController.java で「Create Test」ダイアログを表示し、テストクラスを作成します。
src/test/groovy/ksbysample/webapp/lending/web/confirmresult の下に ConfirmresultControllerTest.groovy が作成されますので、以下の内容に変更します。
package ksbysample.webapp.lending.web.confirmresult import ksbysample.common.test.rule.db.TestData import ksbysample.common.test.rule.db.TestDataResource import ksbysample.common.test.rule.mockmvc.SecurityMockMvcResource import ksbysample.webapp.lending.Application import org.junit.Rule import org.junit.experimental.runners.Enclosed import org.junit.runner.RunWith import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.SpringApplicationContextLoader import org.springframework.test.context.ContextConfiguration import org.springframework.test.context.web.WebAppConfiguration import spock.lang.Specification import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.* @RunWith(Enclosed) class ConfirmresultControllerTest { @ContextConfiguration(loader = SpringApplicationContextLoader.class, classes = Application.class) @WebAppConfiguration static class 貸出申請結果確認画面の初期表示のテスト_正常処理 extends Specification { @Autowired @Rule public TestDataResource testDataResource; @Autowired @Rule public SecurityMockMvcResource mvc; @TestData("src/test/resources/ksbysample/webapp/lending/web/confirmresult/testdata/001") def "貸出申請結果確認画面が表示される"() { expect: mvc.authTanakaTaro.perform(get("/confirmresult?lendingAppId=105")) .andExpect(status().isOk()) .andExpect(content().contentType("text/html;charset=UTF-8")) .andExpect(view().name("confirmresult/confirmresult")) .andExpect(model().hasNoErrors()) } } }
src/test/resources/ksbysample/webapp/lending/web の下に confirmresult/testdata/001 ディレクトリを作成し、現在の DB のデータを CSV ファイルとして保存します。以下の3ファイルを作成します。
■lending_app.csv
lending_app_id,status,lending_user_id,approval_user_id,version 105,4,1,2,2
■lending_book.csv
lending_book_id,lending_app_id,isbn,book_name,lending_state,lending_app_flg,lending_app_reason,approval_result,approval_reason,version 522,105,978-4-7741-5377-3,JUnit実践入門,蔵書あり,1,開発で使用する為,2,購入済です,2 524,105,978-4-7973-4778-4,アジャイルソフトウェア開発の奥義,蔵書あり,1,勉強の為,1,,2 525,105,978-4-87311-704-1,Javaによる関数型プログラミング,蔵書あり,1,勉強会の調査の為,1,,2 521,105,978-4-7741-6366-6,GitHub実践入門,蔵書なし,[null],[null],[null],[null],1 523,105,978-4-7973-8014-9,Java最強リファレンス,蔵書あり,,[null],[null],[null],1
■table-ordering.txt
lending_app lending_book
テストを実行します。「貸出申請結果確認画面の初期表示のテスト_正常処理」テストクラスのクラス名の左側に表示されているアイコンをクリックしてコンテキストメニューを表示後「Run 'ConfirmresultControllerTest$算出...' with Coverage」を選択します。
NullPointerException が発生してテストが失敗しました。
NullPointerException が発生した SecurityMockMvcResource.java:41 を見ると以下の実装でした。
調べてみると テストクラスで @Rule + @Autowired を組み合わせて SecurityMockMvcResource を宣言した時に @Autowired の方が効いていないようです。Debug モードでテストを実行すると @Autowired アノテーションを付けて宣言しているフィールドが全て null になっていることが確認できます ( 変数の値が簡単に見られて IntelliJ IDEA の debug モードは便利ですね )。
個人的には場合に応じて Spock, JUnit を使い分ければよいと考えているので、ConfirmresultController クラスのテストは JUnit で作成します。作成した src/test/groovy/ksbysample/webapp/lending/web/confirmresult/ConfirmresultControllerTest.groovy は削除します。
ConfirmresultController クラスのテストの作成
src/main/java/ksbysample/webapp/lending/web/confirmresult の下の ConfirmresultController.java で「Create Test」ダイアログを表示し、テストクラスを作成します。
src/test/java/ksbysample/webapp/lending/web/confirmresult の下に ConfirmresultControllerTest.java が作成されます。
最初にテストの構成を決めます。src/test/java/ksbysample/webapp/lending/web/confirmresult の下の ConfirmresultControllerTest.java を リンク先のその1の内容 に変更します。
テストを実装します。src/test/java/ksbysample/webapp/lending/web/confirmresult の下の ConfirmresultControllerTest.java を リンク先のその2の内容 に変更します。
テスト作成中に気付きましたが、以前 Spring Boot で書籍の貸出状況確認・貸出申請する Web アプリケーションを作る ( その51 )( 貸出申請結果確認画面の作成3 ) で DatabaseConnection クラスのインスタンスを生成する際に dbUnit の allowEmptyFields の機能を有効にしましたが、それを TestDataLoader クラスにも実装するのを忘れていましたので反映します。
src/test/java/ksbysample/common/test/rule/db の下に DbUnitUtils.java を作成します。作成後、リンク先の内容 に変更します。
src/test/java/ksbysample/common/test/rule/db の下の TestDataResource.java を リンク先の内容 に変更します。
src/main/java/ksbysample/common/test/rule/db の下の TestDataLoader.java を リンク先の内容 に変更します。
ブラウザからファイルをダウンロードした時には何もエラーは出なかったのですが、テストクラスから実行するとエラーが発生することも判明したので修正します。
src/main/java/ksbysample/webapp/lending/helper/download/booklistcsv の下の BookListCsvDownloadHelper.java を リンク先の内容 に変更します。
src/main/java/ksbysample/webapp/lending/view の下の BookListCsvView.java を リンク先のその1の内容 に変更します。
テストを実行します。ConfirmresultControllerTest クラスのクラス名の左側に表示されているアイコンをクリックしてコンテキストメニューを表示後「Run 'ConfirmresultControllerTest' with Coverage」を選択します。
テストが成功することが確認できます。
今回のテスト結果を見て以下の点に気付きました。
- "CSVダウンロードAbstractViewをクリックした場合()" テストメソッドは 1s 990ms かかりましたが、次の "CSVダウンロードHttpServletResponseをクリックした場合()" テストメソッドは 251ms で終了しました。サブクラスの最初のテストメソッドは実行完了まで時間がかかっていますが、ほとんど同じ処理である次のテストメソッドは時間がかかっていません。
- 次に実行された "貸出申請結果確認画面の初期表示のテスト_正常処理" サブクラスの最初のテストメソッドは 1s 444ms と再び 1秒台に戻りました。
テストをサブクラスに分けると分かりやすい構成にはなりますが、その分テストに時間がかかるようです。テストの実行速度を上げたい場合には、サブクラスの数は増やさないようにした方がよいみたいですね。
一旦 commit します。
全てのテストが成功するか確認する
最後に全てのテストが成功するか確認します。Project View のルートでコンテキストメニューを表示して「Run 'All Tests' with Coverage」を選択します。
テストが実行され、全て成功することが確認できます。
clean タスクの実行→「Rebuild Project」メニューの実行→build タスクの実行を行います。
"BUILD SUCCESSFUL" のメッセージは出力されましたが、無検査キャストの警告も出ました。
警告の出たソースを修正します。src/main/java/ksbysample/webapp/lending/view の下の BookListCsvView.java を リンク先のその2の内容 に変更します。
再度 clean タスクの実行→「Rebuild Project」メニューの実行→build タスクの実行を行います。
今度は警告が出ずに "BUILD SUCCESSFUL" のメッセージが出力されることが確認できます。
一旦 commit します。
commit、Push、Pull Request、マージ
- GitHub へ Push、feature/81-issue -> 1.0.x へ Pull Request、1.0.x でマージ、feature/81-issue ブランチを削除、をします。
次回は。。。
- 以下のソフトウェアがバージョアンアップしているので、バージョンアップを実施します。
- 次に何点か改善したいと思ったことを Issue に書いたので、それらに対応します。
- その後はやり残したことがないかこれまでの記事を見直し、ないようならば Windows で本番稼働させるためのディレクトリ作成、jar ファイル配置、bat ファイル作成、サービス登録、動作確認へと進む予定です。
メモ書き
- ここ最近は 1.0.x ブランチにマージする前に rebase でコミットをまとめないようにしてみたのですが、blog の記事毎の変更内容を確認したい時にはまとめない方が分かりやすくてよいのですが、実際の開発ではまとめておかないと revert で取り消したい場合等にかなり不便かなと思っています。
ソースコード
build.gradle
task printClassWhatNotMakeTest << { def srcDir = new File("src/main/java"); def excludePaths = [ "src/main/java/ksbysample/webapp/lending/Application.java" , "src/main/java/ksbysample/webapp/lending/config" , "src/main/java/ksbysample/webapp/lending/cookie" , "src/main/java/ksbysample/webapp/lending/dao" , "src/main/java/ksbysample/webapp/lending/entity" , "src/main/java/ksbysample/webapp/lending/exception" , "src/main/java/ksbysample/webapp/lending/helper/download/booklistcsv" , "src/main/java/ksbysample/webapp/lending/helper/download/DataDownloadHelper.java" , "src/main/java/ksbysample/webapp/lending/helper/page/PagenationHelper.java" , "src/main/java/ksbysample/webapp/lending/security/LendingUser.java" , "src/main/java/ksbysample/webapp/lending/security/RoleAwareAuthenticationSuccessHandler.java" , "src/main/java/ksbysample/webapp/lending/service/calilapi/response" , "src/main/java/ksbysample/webapp/lending/service/file/BooklistCSVRecord.java" , "src/main/java/ksbysample/webapp/lending/service/openweathermapapi" , "src/main/java/ksbysample/webapp/lending/service/queue/InquiringStatusOfBookQueueMessage.java" , "src/main/java/ksbysample/webapp/lending/util/doma" , "src/main/java/ksbysample/webapp/lending/util/velocity/VelocityUtils.java" , "src/main/java/ksbysample/webapp/lending/values/validation/ValuesEnum.java" , "src/main/java/ksbysample/webapp/lending/view/BookListCsvView.java" , "src/main/java/ksbysample/webapp/lending/web/.+/.+Service.java" , "src/main/java/ksbysample/webapp/lending/webapi/common/CommonWebApiResponse.java" , "src/main/java/ksbysample/webapp/lending/webapi/weather" ]; def excludeFileNamePatterns = [ ".*EventListener.java" , ".*Dto.java" , ".*Form.java" , ".*Values.java" ]; compareSrcAndTestDir(srcDir, excludePaths, excludeFileNamePatterns); }
- 以下の3行を配列 excludePaths に追加します。
, "src/main/java/ksbysample/webapp/lending/helper/download/booklistcsv"
, "src/main/java/ksbysample/webapp/lending/helper/download/DataDownloadHelper.java"
, "src/main/java/ksbysample/webapp/lending/view/BookListCsvView.java"
ConfirmresultControllerTest.java
■その1
package ksbysample.webapp.lending.web.confirmresult; import ksbysample.common.test.rule.db.NoUseTestDataResource; import ksbysample.webapp.lending.Application; import org.junit.Test; import org.junit.experimental.runners.Enclosed; import org.junit.runner.RunWith; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; @RunWith(Enclosed.class) public class ConfirmresultControllerTest { @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = Application.class) @WebAppConfiguration public static class 貸出申請結果確認画面の初期表示のテスト_エラー処理 { @Test @NoUseTestDataResource public void ログインしていなければ貸出申請結果確認画面は表示できない() throws Exception { } @Test @NoUseTestDataResource public void lendingAppIdパラメータがなければエラーになる() throws Exception { } @Test @NoUseTestDataResource public void lendingAppIdパラメータで指定された値が数値でなければエラーになる() throws Exception { } @Test public void lendingAppIdパラメータで指定されたデータが登録されていなければエラーになる() throws Exception { } @Test public void 申請者でなければ貸出申請結果確認画面を表示できない() throws Exception { } } @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = Application.class) @WebAppConfiguration public static class 貸出申請結果確認画面の初期表示のテスト_正常処理 { @Test public void lendingAppIdパラメータで指定されたデータが登録されており申請者ならば貸出申請結果確認画面が表示される() throws Exception { } } @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = Application.class) @WebAppConfiguration public static class 貸出申請結果確認画面の正常処理時のテスト { @Test public void CSVダウンロード_HttpServletResponse_をクリックした場合() throws Exception { } @Test public void CSVダウンロード_AbstractView_をクリックした場合() throws Exception { } } }
■その2
package ksbysample.webapp.lending.web.confirmresult; import com.google.common.base.Charsets; import ksbysample.common.test.helper.TestHelper; import ksbysample.common.test.rule.db.NoUseTestDataResource; import ksbysample.common.test.rule.db.TestData; import ksbysample.common.test.rule.db.TestDataResource; import ksbysample.common.test.rule.mockmvc.SecurityMockMvcResource; import ksbysample.webapp.lending.Application; import ksbysample.webapp.lending.helper.message.MessagesPropertiesHelper; import org.junit.Rule; import org.junit.Test; import org.junit.experimental.runners.Enclosed; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MvcResult; import org.yaml.snakeyaml.Yaml; import java.io.File; import java.nio.charset.Charset; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.containsString; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @RunWith(Enclosed.class) public class ConfirmresultControllerTest { @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = Application.class) @WebAppConfiguration public static class 貸出申請結果確認画面の初期表示のテスト_エラー処理 { @Rule @Autowired public TestDataResource testDataResource; @Rule @Autowired public SecurityMockMvcResource mvc; @Autowired private MessagesPropertiesHelper messagesPropertiesHelper; @Test @NoUseTestDataResource public void ログインしていなければ貸出申請結果確認画面は表示できない() throws Exception { mvc.noauth.perform(get("/confirmresult?lendingAppId=105")) .andExpect(status().isFound()) .andExpect(redirectedUrl("http://localhost/")); } @Test @NoUseTestDataResource public void lendingAppIdパラメータがなければエラーになる() throws Exception { mvc.authTanakaTaro.perform(get("/confirmresult")) .andExpect(status().isOk()) .andExpect(content().contentType("text/html;charset=UTF-8")) .andExpect(view().name("error")) .andExpect(content().string( containsString(messagesPropertiesHelper.getMessage("ConfirmresultParamForm.lendingAppId.emptyerr", null)))); } @Test @NoUseTestDataResource public void lendingAppIdパラメータで指定された値が数値でなければエラーになる() throws Exception { mvc.authTanakaTaro.perform(get("/confirmresult?lendingAppId=a")) .andExpect(status().isOk()) .andExpect(content().contentType("text/html;charset=UTF-8")) .andExpect(view().name("error")) .andExpect(content().string( containsString(messagesPropertiesHelper.getMessage("ConfirmresultParamForm.lendingAppId.emptyerr", null)))); } @Test @TestData("src/test/resources/ksbysample/webapp/lending/web/confirmresult/testdata/001") public void lendingAppIdパラメータで指定されたデータが登録されていなければエラーになる() throws Exception { mvc.authTanakaTaro.perform(get("/confirmresult?lendingAppId=1")) .andExpect(status().isOk()) .andExpect(content().contentType("text/html;charset=UTF-8")) .andExpect(view().name("confirmresult/confirmresult")) .andExpect(xpath("//*[@id=\"confirmresultForm\"]/div/p") .string(messagesPropertiesHelper.getMessage("ConfirmresultForm.lendingApp.nodataerr", null))); } @Test @TestData("src/test/resources/ksbysample/webapp/lending/web/confirmresult/testdata/001") public void 申請者でなければ貸出申請結果確認画面を表示できない() throws Exception { mvc.authSuzukiHanako.perform(get("/confirmresult?lendingAppId=105")) .andExpect(status().isForbidden()) .andExpect(content().contentType("text/html;charset=UTF-8")) .andExpect(view().name("error")) .andExpect(content().string( containsString(messagesPropertiesHelper.getMessage("Confirmresult.lendingUserId.notequalerr", null)))); } } @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = Application.class) @WebAppConfiguration public static class 貸出申請結果確認画面の初期表示のテスト_正常処理 { @Rule @Autowired public TestDataResource testDataResource; @Rule @Autowired public SecurityMockMvcResource mvc; @Test @TestData("src/test/resources/ksbysample/webapp/lending/web/confirmresult/testdata/001") public void lendingAppIdパラメータで指定されたデータが登録されており申請者ならば貸出申請結果確認画面が表示される() throws Exception { mvc.authTanakaTaro.perform(get("/confirmresult?lendingAppId=105")) .andExpect(status().isOk()) .andExpect(content().contentType("text/html;charset=UTF-8")) .andExpect(view().name("confirmresult/confirmresult")) .andExpect(xpath("//*[@id=\"confirmresultForm\"]/div/div/div[1]/table/tr[2]/td").string("承認済")) .andExpect(xpath("//*[@id=\"confirmresultForm\"]/div/div/div[1]/table/tr[3]/td").string("tanaka taro")) .andExpect(xpath("//*[@id=\"confirmresultForm\"]/div/div/div[1]/table/tr[4]/td").string("suzuki hanako")) .andExpect(xpath("//*[@id=\"confirmresultForm\"]/div/div/table/tbody/tr").nodeCount(3)) .andExpect(xpath("//*[@id=\"confirmresultForm\"]/div/div/table/tbody/tr[1]/td[2]").string("978-4-7741-5377-3")) .andExpect(xpath("//*[@id=\"confirmresultForm\"]/div/div/table/tbody/tr[1]/td[3]").string("JUnit実践入門")) .andExpect(xpath("//*[@id=\"confirmresultForm\"]/div/div/table/tbody/tr[1]/td[4]").string("開発で使用する為")) .andExpect(xpath("//*[@id=\"confirmresultForm\"]/div/div/table/tbody/tr[1]/td[5]").string("却下")) .andExpect(xpath("//*[@id=\"confirmresultForm\"]/div/div/table/tbody/tr[1]/td[6]").string("購入済です")) .andExpect(xpath("//*[@id=\"confirmresultForm\"]/div/div/table/tbody/tr[2]/td[5]").string("承認")); } } @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = Application.class) @WebAppConfiguration public static class 貸出申請結果確認画面の正常処理時のテスト { // テストデータ private ConfirmresultForm confirmresultForm_001 = (ConfirmresultForm) new Yaml().load(getClass().getResourceAsStream("ConfirmresultForm_001.yaml")); @Rule @Autowired public TestDataResource testDataResource; @Rule @Autowired public SecurityMockMvcResource mvc; @Test @TestData("src/test/resources/ksbysample/webapp/lending/web/confirmresult/testdata/001") public void CSVダウンロード_HttpServletResponse_をクリックした場合() throws Exception { MvcResult result = mvc.authTanakaTaro.perform(TestHelper.postForm("/confirmresult/filedownloadByResponse", this.confirmresultForm_001).with(csrf())) .andExpect(status().isOk()) .andExpect(header().string("Content-Disposition", "attachment; filename=\"booklist-105.csv\"")) .andExpect(content().encoding("UTF-8")) .andReturn(); String content = result.getResponse().getContentAsString(); assertThat(content) .isEqualTo(com.google.common.io.Files.toString( new File("src/test/resources/ksbysample/webapp/lending/web/confirmresult/assertdata/001/booklist-105.utf-8.csv") , Charsets.UTF_8)); } @Test @TestData("src/test/resources/ksbysample/webapp/lending/web/confirmresult/testdata/001") public void CSVダウンロード_AbstractView_をクリックした場合() throws Exception { MvcResult result = mvc.authTanakaTaro.perform(TestHelper.postForm("/confirmresult/filedownloadByView", this.confirmresultForm_001).with(csrf())) .andExpect(status().isOk()) .andExpect(header().string("Content-Disposition", "attachment; filename=\"booklist-105.csv\"")) .andExpect(content().encoding("MS932")) .andReturn(); String content = result.getResponse().getContentAsString(); assertThat(content) .isEqualTo(com.google.common.io.Files.toString( new File("src/test/resources/ksbysample/webapp/lending/web/confirmresult/assertdata/002/booklist-105.ms932.csv") , Charset.forName("MS932"))); } } }
DbUnitUtils.java
package ksbysample.common.test.rule.db; import org.dbunit.DatabaseUnitException; import org.dbunit.database.DatabaseConfig; import org.dbunit.database.DatabaseConnection; import org.dbunit.database.IDatabaseConnection; import javax.sql.DataSource; import java.sql.SQLException; public class DbUnitUtils { public static final String NULL_STRING = "[null]"; public static IDatabaseConnection createDatabaseConnection(DataSource dataSource) throws SQLException, DatabaseUnitException { IDatabaseConnection conn = new DatabaseConnection(dataSource.getConnection()); DatabaseConfig databaseConfig = conn.getConfig(); databaseConfig.setProperty(DatabaseConfig.FEATURE_ALLOW_EMPTY_FIELDS, true); return conn; } }
TestDataResource.java
package ksbysample.common.test.rule.db; import org.dbunit.DatabaseUnitException; import org.dbunit.database.IDatabaseConnection; import org.dbunit.database.QueryDataSet; import org.dbunit.dataset.DataSetException; import org.dbunit.dataset.IDataSet; import org.dbunit.dataset.ReplacementDataSet; import org.dbunit.dataset.xml.FlatXmlDataSet; import org.dbunit.dataset.xml.FlatXmlDataSetBuilder; import org.dbunit.operation.DatabaseOperation; import org.junit.rules.TestWatcher; import org.junit.runner.Description; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.sql.DataSource; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.lang.annotation.Annotation; import java.net.MalformedURLException; import java.nio.file.Files; import java.nio.file.Paths; import java.sql.SQLException; import java.util.Collection; import java.util.List; @Component public class TestDataResource extends TestWatcher { private static final String TESTDATA_BASE_DIR = "src/test/resources/testdata/base"; private static final String BACKUP_FILE_NAME = "ksbylending_backup"; @Autowired private DataSource dataSource; @Autowired private TestDataLoader testDataLoader; private File backupFile; @Override protected void starting(Description description) { IDatabaseConnection conn = null; try { // @NouseTestDataResource アノテーションがテストメソッドに付加されていない場合には処理を実行する if (!hasNoUseTestDataResourceAnnotation(description)) { conn = DbUnitUtils.createDatabaseConnection(dataSource); // バックアップを取得する backupDb(conn); // TESTDATA_BASE_DIR で指定されたディレクトリ内のテストデータをロードする testDataLoader.load(TESTDATA_BASE_DIR); // テストメソッドに @TestData アノテーションが付加されている場合には、 // アノテーションで指定されたテストデータをロードする loadTestData(description); } } catch (Exception e) { throw new RuntimeException(e); } finally { try { if (conn != null) conn.close(); } catch (Exception ignored) {} } } @Override protected void finished(Description description) { IDatabaseConnection conn = null; try { // @NouseTestDataResource アノテーションがテストメソッドに付加されていない場合には処理を実行する if (!hasNoUseTestDataResourceAnnotation(description)) { conn = DbUnitUtils.createDatabaseConnection(dataSource); // バックアップからリストアする restoreDb(conn); } } catch (Exception e) { throw new RuntimeException(e); } finally { try { if (conn != null) conn.close(); } catch (Exception ignored) {} if (backupFile != null) { try { Files.delete(backupFile.toPath()); } catch (Exception e) { throw new RuntimeException(e); } backupFile = null; } } } private boolean hasNoUseTestDataResourceAnnotation(Description description) { Collection<Annotation> annotationList = description.getAnnotations(); boolean result = annotationList.stream() .anyMatch(annotation -> annotation instanceof NoUseTestDataResource); return result; } private void backupDb(IDatabaseConnection conn) throws DataSetException, IOException { QueryDataSet partialDataSet = new QueryDataSet(conn); // TESTDATA_BASE_DIR で指定されたディレクトリ内の table-ordering.txt に記述されたテーブル名一覧を取得し、 // バックアップテーブルとしてセットする List<String> backupTableList = Files.readAllLines(Paths.get(TESTDATA_BASE_DIR, "table-ordering.txt")); for (String backupTable : backupTableList) { partialDataSet.addTable(backupTable); } ReplacementDataSet replacementDatasetBackup = new ReplacementDataSet(partialDataSet); replacementDatasetBackup.addReplacementObject(null, DbUnitUtils.NULL_STRING); this.backupFile = File.createTempFile(BACKUP_FILE_NAME, "xml"); try (FileOutputStream fos = new FileOutputStream(this.backupFile)) { FlatXmlDataSet.write(replacementDatasetBackup, fos); } } private void restoreDb(IDatabaseConnection conn) throws MalformedURLException, DatabaseUnitException, SQLException { if (this.backupFile != null) { IDataSet dataSet = new FlatXmlDataSetBuilder().build(this.backupFile); ReplacementDataSet replacementDatasetRestore = new ReplacementDataSet(dataSet); replacementDatasetRestore.addReplacementObject(DbUnitUtils.NULL_STRING, null); DatabaseOperation.CLEAN_INSERT.execute(conn, replacementDatasetRestore); } } private void loadTestData(Description description) { description.getAnnotations().stream() .filter(annotation -> annotation instanceof TestData) .forEach(annotation -> { TestData testData = (TestData)annotation; testDataLoader.load(testData.value()); }); } }
- starting メソッド、finished メソッドの IDatabaseConnection conn の実装クラスのインスタンスを生成する処理を
new DatabaseConnection(dataSource.getConnection());
→DbUnitUtils.createDatabaseConnection(dataSource);
へ変更します。 - ついでに NULL_STRING 定数も DbUnitUtils クラスへ移動し、クラス内の記述を
NULL_STRING
→DbUnitUtils.NULL_STRING
へ変更します。
TestDataLoader.java
package ksbysample.common.test.rule.db; import org.dbunit.database.IDatabaseConnection; import org.dbunit.dataset.IDataSet; import org.dbunit.dataset.ReplacementDataSet; import org.dbunit.dataset.csv.CsvDataSet; import org.dbunit.operation.DatabaseOperation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.sql.DataSource; import java.io.File; @Component public class TestDataLoader { @Autowired private DataSource dataSource; public void load(String csvDir) { IDatabaseConnection conn = null; try { conn = DbUnitUtils.createDatabaseConnection(dataSource); IDataSet dataSet = new CsvDataSet(new File(csvDir)); ReplacementDataSet replacementDataset = new ReplacementDataSet(dataSet); replacementDataset.addReplacementObject(DbUnitUtils.NULL_STRING, null); DatabaseOperation.CLEAN_INSERT.execute(conn, replacementDataset); } catch (Exception e) { throw new RuntimeException(e); } finally { try { if (conn != null) conn.close(); } catch (Exception ignored) {} } } }
- load メソッドの IDatabaseConnection conn の実装クラスのインスタンスを生成する処理を
new DatabaseConnection(dataSource.getConnection());
→DbUnitUtils.createDatabaseConnection(dataSource);
へ変更します。 - NULL_STRING 定数を削除し、クラス内の記述を
NULL_STRING
→DbUnitUtils.NULL_STRING
へ変更します。
BookListCsvDownloadHelper.java
@Override public void writeDataToResponse(HttpServletResponse response) throws IOException { CsvWriterSettings settings = new CsvWriterSettings(); settings.setHeaders(CSV_HEADER); BeanWriterProcessor<BookListCsvData> writerProcessor = new BeanWriterProcessor<>(BookListCsvData.class); settings.setRowWriterProcessor(writerProcessor); response.setCharacterEncoding("UTF-8"); CsvWriter writer = new CsvWriter(response.getWriter(), settings); writer.writeHeaders(); writer.processRecordsAndClose(bookListCsvDataList); }
- writeDataToResponse メソッド内に
response.setCharacterEncoding("UTF-8");
を追加し、response に出力するデータの文字コードを設定します。
BookListCsvView.java
■その1
package ksbysample.webapp.lending.view; import com.univocity.parsers.common.processor.BeanWriterProcessor; import com.univocity.parsers.csv.CsvWriter; import com.univocity.parsers.csv.CsvWriterSettings; import ksbysample.webapp.lending.helper.download.booklistcsv.BookListCsvData; import org.springframework.stereotype.Component; import org.springframework.web.servlet.view.AbstractView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.List; import java.util.Map; @Component(value = "BookListCsvView") public class BookListCsvView extends AbstractView { private static final String[] CSV_HEADER = new String[]{"ISBN", "書名", "申請理由", "承認/却下", "却下理由"}; private static final String CSV_FILE_NAME_FORMAT = "booklist-%s.csv"; @Override protected void renderMergedOutputModel(Map<String, Object> model , HttpServletRequest request, HttpServletResponse response) throws Exception { Long lendingAppId = (Long) model.get("lendingAppId"); List<BookListCsvData> bookListCsvDataList = (List<BookListCsvData>) model.get("bookListCsvDataList"); response.setContentType("application/octet-stream; charset=Windows-31J;"); response.setHeader("Content-Disposition" , String.format("attachment; filename=\"%s\"", String.format(CSV_FILE_NAME_FORMAT, lendingAppId))); CsvWriterSettings settings = new CsvWriterSettings(); settings.setHeaders(CSV_HEADER); BeanWriterProcessor<BookListCsvData> writerProcessor = new BeanWriterProcessor<>(BookListCsvData.class); settings.setRowWriterProcessor(writerProcessor); response.setCharacterEncoding("MS932"); CsvWriter writer = new CsvWriter(response.getWriter(), settings); writer.writeHeaders(); writer.processRecordsAndClose(bookListCsvDataList); } }
response.setCharacterEncoding("MS932");
を追加します。
■その2
@SuppressWarnings("unchecked") @Override protected void renderMergedOutputModel(Map<String, Object> model
履歴
2016/02/11
初版発行。