Spring Boot で書籍の貸出状況確認・貸出申請する Web アプリケーションを作る ( 番外編 )( values パッケージの下にサブパッケージを作成しても ValuesHelper クラスから呼び出せるようにする )
概要
現在の実装では ValuesHelper クラスと同じパッケージ内にある Values列挙型しか ValuesHelper クラスから呼び出すことが出来ませんが、サブパッケージにある Values列挙型も呼び出せるようにします。
またなぜかテストの一部が失敗するようになっていたので、その原因も調査します。
参照したサイト・書籍
package 配下のクラス一覧を取得する方法いろいろ
http://etc9.hatenablog.com/entry/2015/03/31/001620How to check if an object implements an interface?
http://stackoverflow.com/questions/10165887/how-to-check-if-an-object-implements-an-interface
目次
手順
テストの一部が失敗する原因を調査する
Gradle projects View から clean タスクの実行→「Rebuild Project」メニューの実行をした後、Project View のルートでコンテキストメニューを表示して「Run 'All Tests' with Coverage」を選択してテストを実行したところ、テストの一部が失敗することに気づきました。
原因は、いろいろ試していた時に user_info テーブルのデータを変更していたのですが、失敗していたテストメソッドは @Rule @Autowired public TestDataResource testDataResource;
をクラスに指定していなかったり、@NoUseTestDataResource アノテーションを付加していたりして、user_info テーブルにテスト用データがロードされていなかったためでした。
SecurityMockMvcResource の mvc.noauth 以外の mvc.authTanakaTaro 等は userDetailsService.loadUserByUsername(...);
を呼び出しているので、SecurityMockMvcResource を使用する場合にはテスト用データのロードが必須であることに気付いていませんでした。。。
SecurityMockMvcResource を noauth 用とそれ以外用に分けるのが正しい対応だとは思うのですが、今回は失敗しているテストメソッドから @NoUseTestDataResource アノテーションを削除するか、テストクラスに @Rule @Autowired public TestDataResource testDataResource;
が記述されていない場合には追加してテスト用データがロードされるようにします。
feature/112-issue を作成して、ソースを修正します。今回は修正したソースはここには書きません。
修正後、再度 Project View のルートでコンテキストメニューを表示して「Run 'All Tests' with Coverage」を選択し、テストを実行してテストが全て成功することを確認します。
commit、GitHub へ Push、feature/112-issue -> 1.0.x へ Pull Request、1.0.x でマージ、feature/112-issue ブランチを削除、をします。
values パッケージの下にサブパッケージを作成しても ValuesHelper クラスから呼び出せるようにする
package 配下のクラス一覧を取得する方法いろいろ の記事によると、Guava の ClassPath クラスには getTopLevelClasses メソッド以外に getTopLevelClassesRecursive というメソッドがあり、getTopLevelClassesRecursive を使えばサブパッケージのクラスも取得できるようになるとのこと。Spring に ClassPathScanningCandidateComponentProvider というクラスが用意されているらしいですが、今回はメソッドを getTopLevelClassesRecursive に変更する対応にします。
feature/111-issue ブランチを作成します。
src/main/java/ksbysample/webapp/lending/values の下の ValuesHelper.java を リンク先の内容 に変更します。
src/main/java/ksbysample/webapp/lending/values の下のパッケージ構成、クラス配置を以下のように変更します。
■変更前
■変更後
動作確認します。Gradle projects View から clean タスクの実行→「Rebuild Project」メニューの実行→build タスクの実行を行い、"BUILD SUCCESSFUL" のメッセージが出力されることを確認します。
Project View のルートでコンテキストメニューを表示して「Run 'All Tests' with Coverage」を選択し、テストが全て成功することを確認します。
commit、GitHub へ Push、feature/111-issue -> 1.0.x へ Pull Request、1.0.x でマージ、feature/111-issue ブランチを削除、をします。
ソースコード
ValuesHelper.java
package ksbysample.webapp.lending.values; import com.google.common.reflect.ClassPath; import org.springframework.stereotype.Component; import java.io.IOException; import java.util.Map; import java.util.stream.Collectors; @Component("vh") public class ValuesHelper { private final Map<String, String> valuesObjList; private ValuesHelper() throws IOException { ClassLoader loader = Thread.currentThread().getContextClassLoader(); valuesObjList = ClassPath.from(loader).getTopLevelClassesRecursive(this.getClass().getPackage().getName()) .stream() .filter(classInfo -> { try { Class<?> clazz = Class.forName(classInfo.getName()); return !clazz.equals(Values.class) && Values.class.isAssignableFrom(clazz); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } }) .collect(Collectors.toMap(ClassPath.ClassInfo::getSimpleName, ClassPath.ClassInfo::getName)); } @SuppressWarnings("unchecked") public <T extends Enum<T> & Values> String getValue(String classSimpleName, String valueName) throws ClassNotFoundException { Class<T> enumType = (Class<T>) Class.forName(this.valuesObjList.get(classSimpleName)); T val = Enum.valueOf(enumType, valueName); return val.getValue(); } public <T extends Enum<T> & Values> String getValue(Class<T> enumType, String valueName) { T val = Enum.valueOf(enumType, valueName); return val.getValue(); } @SuppressWarnings("unchecked") public <T extends Enum<T> & Values> String getText(String classSimpleName, String value) throws ClassNotFoundException { Class<T> enumType = (Class<T>) Class.forName(this.valuesObjList.get(classSimpleName)); String result = ""; for (T val : enumType.getEnumConstants()) { if (val.getValue().equals(value)) { result = val.getText(); break; } } return result; } public <T extends Enum<T> & Values> String getText(Class<T> enumType, String value) { String result = ""; for (T val : enumType.getEnumConstants()) { if (val.getValue().equals(value)) { result = val.getText(); break; } } return result; } @SuppressWarnings("unchecked") public <T extends Enum<T> & Values> T[] values(String classSimpleName) throws ClassNotFoundException { Class<T> enumType = (Class<T>) Class.forName(this.valuesObjList.get(classSimpleName)); return enumType.getEnumConstants(); } }
- コンストラクタ ValuesHelper() の以下の点を変更します。
getTopLevelClasses
→getTopLevelClassesRecursive
へ変更します。- filter メソッドで ValuesHelper クラスだけを除外するようにしていた処理を、Values インターフェースを implements していて、かつ Values インターフェース自体は除くクラスだけを抽出する処理に変更します。
履歴
2016/03/12
初版発行。