Spring Boot で書籍の貸出状況確認・貸出申請する Web アプリケーションを作る ( その25 )( 貸出希望書籍 CSV ファイルアップロード画面の作成4 )
概要
Spring Boot で書籍の貸出状況確認・貸出申請する Web アプリケーションを作る ( その24 )( 貸出希望書籍 CSV ファイルアップロード画面の作成3 ) の続きです。
- 今回の手順で確認できるのは以下の内容です。
- 貸出希望書籍 CSV ファイルアップロード画面の作成
- テストを作成します。
- 貸出希望書籍 CSV ファイルアップロード画面の作成
参照したサイト・書籍
How to diff files/folders in Gradle?
http://stackoverflow.com/questions/30401436/how-to-diff-files-folders-in-gradlecomparedirs.groovy
https://gist.github.com/igormukhin/71d780c4274336eeb297Java開発の強力な相棒として今すぐ使えるGroovy - SlideShare
http://www.slideshare.net/nobeans/javagroovyUsing Spring MVC Test to unit test multipart POST request
http://stackoverflow.com/questions/21800726/using-spring-mvc-test-to-unit-test-multipart-post-request- MultipartFile クラスを使用するテストをするための方法を参照しました。MockMultipartFile でモックを作成してテストします。
Java Code Examples for org.springframework.validation.BeanPropertyBindingResult
http://www.programcreek.com/java-api-examples/index.php?api=org.springframework.validation.BeanPropertyBindingResult- Errors インターフェースの実体オブジェクトを MapBindingResult クラスで生成する方法を参考にしました。
目次
- バックアップ、リストア対象のテーブルを追加し、テストデータを用意する
- テスト未作成のクラス一覧を出力する Gradle タスクを作成する
- テスト作成対象のクラスを決める
- MessagesPropertiesHelper クラスのテストの作成
- BooklistCsvFileService クラスのテストの作成
- 次回は。。。
手順
バックアップ、リストア対象のテーブルを追加し、テストデータを用意する
lending_app, lending_book テーブルをバックアップ、リストアの対象にします。src/test/java/ksbysample/common/test の下の TestDataResource.java を リンク先の内容 に変更します。
テストデータを用意します。lending_app, lending_book の初期データは空にします。src/test/resources/testdata/base の下に lending_app.csv, lending_book.csv を作成します。作成後、リンク先の内容 に変更します。
テスト未作成のクラス一覧を出力する Gradle タスクを作成する
テスト未作成のクラス一覧を出力してくれる機能が欲しくなったので、Gradle タスクで実装することにします。build.gradle を リンク先のその1、その2の内容 に変更します。
作成する printClassWhatNotMakeTest タスクは以下の仕様です。
Gradle projects View の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。
テスト作成対象のクラスを決める
Gradle projects View から printClassWhatNotMakeTest タスクを実行します。
出力されたクラスの内、以下のクラスのテストを作成することにします。
MessagesPropertiesHelper クラスのテストの作成
src/main/java/ksbysample/webapp/lending/helper/message の下の MessagesPropertiesHelper.java で「Create Test」ダイアログを表示し、テストクラスを作成します。
src/test/java/ksbysample/webapp/lending/helper/message の下に MessagesPropertiesHelperTest.java が作成されます。
src/test/java/ksbysample/webapp/lending/helper/message の下の MessagesPropertiesHelperTest.java を リンク先の内容 に変更します。
テストを実行します。MessagesPropertiesHelperTest クラスのクラス名にカーソルを移動し、コンテキストメニューを表示後「Run 'MessagesPropertiesHelperTest' with Coverage」を選択します。
テストが成功することが確認できます。
BooklistCsvFileService クラスのテストの作成
src/main/java/ksbysample/webapp/lending/service/file の下の BooklistCsvFileService.java で「Create Test」ダイアログを表示し、テストクラスを作成します。
src/test/java/ksbysample/webapp/lending/service/file の下に BooklistCsvFileServiceTest.java が作成されます。
テストのためにコンストラクタが必要なので追加します。src/main/java/ksbysample/webapp/lending/service/file の下の BooklistCSVRecord.java を リンク先の内容 に変更します。
src/test/java/ksbysample/webapp/lending/service/file の下の BooklistCsvFileServiceTest.java を リンク先の内容 に変更します。
テストを実行します。BooklistCsvFileServiceTest クラスのクラス名にカーソルを移動し、コンテキストメニューを表示後「Run 'BooklistCsvFileServiceTest' with Coverage」を選択します。
テストが成功することが確認できます。
次回は。。。
テストの作成が続きます。
ソースコード
TestDataResource.java
Component public class TestDataResource extends ExternalResource { private final String TESTDATA_DIR = "src/test/resources/testdata/base"; private final String BACKUP_FILE_NAME = "ksbylending_backup"; private final List<String> BACKUP_TABLES = Arrays.asList( "user_info" , "user_role" , "library_forsearch" , "lending_app" , "lending_book" );
- BACKUP_TABLES の配列に "lending_app", "lending_book" を追加します。
lending_app.csv, lending_book.csv
■lending_app.csv
lending_app_id,status,lending_user_id,approval_user_id
■lending_book.csv
lending_book_id,lending_app_id,isbn,book_name,lending_state,lending_app_flg,lending_app_reason,approval_result,approval_reason
build.gradle
■その1
task downloadCssFontsJs << { ..... } task printClassWhatTestNotMake << { 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/dao" , "src/main/java/ksbysample/webapp/lending/entity" , "src/main/java/ksbysample/webapp/lending/exception" , "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/Librar" , "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/webapi/common/CommonWebApiResponse.java" , "src/main/java/ksbysample/webapp/lending/webapi/weather" ]; def excludeFileNamePatterns = [ ".*EventListener.java" , ".*Form.java" , ".*Values.java" ]; compareSrcAndTestDir(srcDir, excludePaths, excludeFileNamePatterns); }
- downloadCssFontsJs タスクの下に printClassWhatTestNotMake タスクを追加します。
■その2
void downloadBootstrapFileInputMinJs(String workDirPath, String staticDirPath) { ..... } def compareSrcAndTestDir(srcDir, excludePaths, excludeFileNamePatterns) { def existFlg; for (srcFile in srcDir.listFiles()) { String srcFilePath = (srcFile.toPath() as String).replaceAll("\\\\", "/"); existFlg = false; for (exclude in excludePaths) { if (srcFilePath =~ /^${exclude as String}/) { existFlg = true; break; } } if (existFlg == true) continue; for (exclude in excludeFileNamePatterns) { if (srcFilePath =~ /${exclude as String}/) { existFlg = true break; } } if (existFlg == true) continue; if (srcFile.isDirectory()) { compareSrcAndTestDir(srcFile, excludePaths, excludeFileNamePatterns); } else { String testFilePath = srcFilePath.replaceFirst(/^src\/main\/java/, "src/test/java").replaceFirst(/\.java$/, "Test.java"); def testFile = new File(testFilePath); if (!testFile.exists()) { println(srcFilePath); } } } }
- downloadBootstrapFileInputMinJs 関数の下に compareSrcAndTestDir関数を追加します。
MessagesPropertiesHelperTest.java
package ksbysample.webapp.lending.helper.message; import ksbysample.webapp.lending.Application; import org.junit.Test; 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 static org.assertj.core.api.Assertions.assertThat; @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = Application.class) @WebAppConfiguration public class MessagesPropertiesHelperTest { @Autowired private MessagesPropertiesHelper messagesPropertiesHelper; @Test public void testGetMessage_NoArgs() throws Exception { String message = messagesPropertiesHelper.getMessage("AbstractUserDetailsAuthenticationProvider.locked" , null); assertThat(message).isEqualTo("入力された ID はロックされています"); } @Test public void testGetMessage_Args() throws Exception { int line = 1; int length = 3; String message = messagesPropertiesHelper.getMessage("UploadBooklistForm.fileupload.lengtherr" , new Object[]{line, length}); assertThat(message).isEqualTo("1行目のレコードの項目数が 2個ではありません ( 3個 )。"); } }
BooklistCSVRecord.java
package ksbysample.webapp.lending.service.file; import com.univocity.parsers.annotations.Parsed; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor public class BooklistCSVRecord { @Parsed private String isbn; @Parsed(field = "書名") private String bookName; }
- lombok の @NoArgsConstructor, @AllArgsConstructor アノテーションを付加します。
BooklistCsvFileServiceTest.java
package ksbysample.webapp.lending.service.file; import com.univocity.parsers.csv.CsvWriter; import com.univocity.parsers.csv.CsvWriterSettings; import ksbysample.webapp.lending.Application; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.mock.web.MockMultipartFile; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.validation.Errors; import org.springframework.validation.MapBindingResult; import org.springframework.validation.ObjectError; import java.io.BufferedWriter; import java.io.InputStream; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; import java.util.HashMap; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = Application.class) @WebAppConfiguration public class BooklistCsvFileServiceTest { @Autowired private BooklistCsvFileService booklistCsvFileService; @Test public void testValidateUploadFile_NoErrorCsvFile() throws Exception { MockMultipartFile multipartFile = createNoErrorCsvFile(); Errors errors = new MapBindingResult(new HashMap<String, String>(), ""); booklistCsvFileService.validateUploadFile(multipartFile, errors); assertThat(errors.hasErrors()).isFalse(); } @Test public void testValidateUploadFile_ErrorCsvFile() throws Exception { MockMultipartFile multipartFile = createErrorCsvFile(); Errors errors = new MapBindingResult(new HashMap<String, String>(), ""); booklistCsvFileService.validateUploadFile(multipartFile, errors); assertThat(errors.hasErrors()).isTrue(); assertThat(errors.getErrorCount()).isEqualTo(6); assertThat(errors.getAllErrors()) .contains(new ObjectError("", new String[]{"UploadBooklistForm.fileupload.lengtherr"}, new Object[]{2, 3}, null)) .contains(new ObjectError("", new String[]{"UploadBooklistForm.fileupload.isbn.patternerr"}, new Object[]{3, "978-4-7741-5x77-3"}, null)) .contains(new ObjectError("", new String[]{"UploadBooklistForm.fileupload.isbn.lengtherr"}, new Object[]{4, "978-4-79173-8014-9"}, null)) .contains(new ObjectError("", new String[]{"UploadBooklistForm.fileupload.isbn.numlengtherr"}, new Object[]{4, "97847917380149"}, null)) .contains(new ObjectError("", new String[]{"UploadBooklistForm.fileupload.isbn.numlengtherr"}, new Object[]{5, "97847197347784"}, null)) .contains(new ObjectError("", new String[]{"UploadBooklistForm.fileupload.bookname.lengtherr"}, new Object[]{6, "123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"}, null)); } @Test public void testConvertFileToList() throws Exception { MockMultipartFile multipartFile = createNoErrorCsvFile(); List<BooklistCSVRecord> booklistCSVRecordList = booklistCsvFileService.convertFileToList(multipartFile); assertThat(booklistCSVRecordList).hasSize(5); assertThat(booklistCSVRecordList).contains(new BooklistCSVRecord("978-4-7741-5377-3", "JUnit実践入門")); } private MockMultipartFile createNoErrorCsvFile() throws Exception { Path path = Files.createTempFile("テスト", "csv"); try (BufferedWriter bw = Files.newBufferedWriter(path, Charset.forName("Windows-31J"))) { CsvWriterSettings settings = new CsvWriterSettings(); settings.setQuoteAllFields(true); CsvWriter writer = new CsvWriter(bw, settings); writer.writeHeaders("ISBN", "書名"); writer.writeRow("978-4-7741-6366-6", "GitHub実践入門"); writer.writeRow("978-4-7741-5377-3", "JUnit実践入門"); writer.writeRow("978-4-7973-8014-9", "Java最強リファレンス"); writer.writeRow("978-4-7973-4778-4", "アジャイルソフトウェア開発の奥義"); writer.writeRow("978-4-87311-704-1", "Javaによる関数型プログラミング"); writer.close(); } MockMultipartFile multipartFile; try (InputStream is = Files.newInputStream(path)) { multipartFile = new MockMultipartFile("テスト.csv", is); } return multipartFile; } private MockMultipartFile createErrorCsvFile() throws Exception { Path path = Files.createTempFile("テスト2", "csv"); try (BufferedWriter bw = Files.newBufferedWriter(path, Charset.forName("Windows-31J"))) { CsvWriterSettings settings = new CsvWriterSettings(); settings.setQuoteAllFields(true); CsvWriter writer = new CsvWriter(bw, settings); writer.writeHeaders("ISBN", "書名"); writer.writeRow("978-4-7741-6366-6", "GitHub実践入門", "項目が3つ"); writer.writeRow("978-4-7741-5x77-3", "JUnit実践入門"); writer.writeRow("978-4-79173-8014-9", "Java最強リファレンス"); writer.writeRow("978-4-719734778-4", "アジャイルソフトウェア開発の奥義"); writer.writeRow("978-4-87311-704-1", "123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"); writer.close(); } MockMultipartFile multipartFile; try (InputStream is = Files.newInputStream(path)) { multipartFile = new MockMultipartFile("テスト2.csv", is); } return multipartFile; } }
- テストのポイントを以下に記載します。
- テスト用の MultipartFile のインスタンスは org.springframework.mock.web.MockMultipartFile を利用して生成します。
- CSV ファイルは Files.createTempFile で Path オブジェクトを取得した後、Files.newBufferedWriter と uniVocity-parsers を利用して作成します。作成後、Files.newInputStream で MultipartFile のインスタンスに読み込みます。
- Errors クラスのインスタンスは MapBindingResult クラスで生成します。第2引数は空文字列で構いません。文字列を指定した場合には、その後の new ObjectError の第1引数に同じ文字列を指定します。
履歴
2015/10/08
初版発行。