- 投稿日:2020-10-14T20:49:57+09:00
PowerMockでEnumをモック化する
概要
PowerMockを導入すれば、Enumクラスをモック化してJUnitでテストコードを作成できます。
環境
バージョン Java 8 JUnit 4.12 Mockito 2.23 PowerMock 2.0.2 使用ライブラリ一覧
- byte-buddy-1.10.14.jar
- cglib-nodep-3.2.9.jar
- hamcrest-core-1.3.jar
- javassist-3.24.0-GA.jar
- junit-4.12.jar
- mockito-core-2.23.0.jar
- objenesis-3.0.1.jar
- powermock-mockito2-2.0.2-full.jar
サンプルコード
モック化対象クラス
public enum SampleEnum { ELEM1("val1"), ELEM2("val2"); private String val; private SampleEnum(String val) { this.val = val; } public String getVal() { return this.val; } }テストクラス
@RunWith(PowerMockRunner.class) @PrepareForTest(SampleEnum.class) public class SampleEnumTest { @Test public void test() throws Exception { // 期待値 String expected_elem1 = "test"; String expected_elem2 = "val2"; // Enumをモック化する SampleEnum mocked = Mockito.mock(SampleEnum.class); // メソッドに戻り値を設定してスタブ化する Mockito.when(mocked.getVal()).thenReturn(expected_elem1); // スタブ化しない場合 // Mockito.when(mocked.getVal()).thenCallRealMethod(); // Enumの要素にモック化したオブジェクトをセットする Whitebox.setInternalState(SampleEnum.class, "ELEM1", mocked); // 実行 String actual_elem1 = SampleEnum.ELEM1.getVal(); // "test"が返る String actual_elem2 = SampleEnum.ELEM2.getVal(); // "val2"が返る // 結果確認 assertEquals(expected_elem1, actual_elem1); assertEquals(expected_elem2, actual_elem2); } }ポイント①
実はEnumのモック化それ自体には、PowerMockライブラリを使用していません。
Mockito.mock(SampleEnum.class)
のように、Mockitoライブラリでモック化可能です。
(Mockitoの代わりにPowerMockito.mock、PowerMockito.whenを使用しても動作します。)ポイントは、
@Runwith
にPowerMockRunnner.classを指定し、@PrepareForTest
にモック化したいEnumクラスを記述する点にあります。後は、
thenReturn()
で戻り値を設定したり、thenCallRealMethod()
でスタブ化せずに実物のメソッドを呼び出すことも可能です。試しに
@Runwith
と@PrepareForTest
をコメントアウトし、実行してみると以下のエラーログが吐かれます。org.mockito.exceptions.base.MockitoException: Cannot mock/spy class jp.co.sample_powermock.enumtest.SampleEnum Mockito cannot mock/spy because : - final classJUnit4のテストランナーはEnumクラスのモック化に対応していないようです。
ポイント②
Enumの定数は1つ1つがEnumのインスタンスと考えることができます。
モック化したEnumオブジェクトを、このインスタンスそれぞれに注入することで、Enumのモック化が成立します。サンプルコードではPowerMockライブラリの
Whitebox.setInternalState()
を使用して、Enumの要素"ELEM1"にモックオブジェクトを注入しています。その結果、
SampleEnum.ELEM1
は設定したモックオブジェクトの振る舞い、SampleEnum.ELEM2
は実物の通りに振る舞います。Whiteboxを使用せずとも、
java.lang.reflect
でモックオブジェクトを設定することもできますが、Whiteboxを使用する方がコードを簡潔に保てると思います。
- 投稿日:2020-10-14T20:45:37+09:00
Mockito3.4でstaticメソッドをモック化する
概要
Mockitoのバージョン3.4からstaticメソッドのモック化に対応しました。
これにより、staticメソッドのモック化のみならPowerMockを導入する必要はなくなりました。環境
- mockito-core-3.4.0.jar
- byte-buddy-1.10.16.jar
上記jarに加え、新たに
mockito-inline
とbyte-buddy-agent
が必要になります。
- mockito-inline-3.4.0.jar
- byte-buddy-agent-1.10.16.jarそれぞれ、Mockito公式サイト・ByteBuddy公式サイトからダウンロードできます。
バージョン java 8 JUnit 5 サンプルコード
モック化対象のクラス
staticメソッドを含みます。
public class MockedClass { public static String methodA() { return "val1"; } public static String methodB() { return "val2"; } public static String methodC(String str) { return str; } }テストクラス
public class MockedClassTest { @Test public void test() throws Exception { // 期待値 String expected_methodA = "test"; String expected_methodB = "val2"; // 対象クラスのモック化 MockedStatic<MockedClass> mocked = Mockito.mockStatic(MockedClass.class); // 戻り値を設定してスタブ化 mocked.when(MockedClass::methodA).thenReturn(expected_methodA); // スタブ化しない場合 mocked.when(MockedClass::methodB).thenCallRealMethod(); // 実行 String actual_methodA = MockedClass.methodA(); String actual_methodB = MockedClass.methodB(); // 結果確認 assertEquals(expected_methodA, actual_methodA); assertEquals(expected_methodB, actual_methodB); } }モック化するには
Mockito.mockStatic()
戻り値の設定にはthenReturn()
戻り値を設定せず、実物を呼びたい場合はthenCallRealMethod()
を使用できます。staticメソッドの指定方法
サンプルコードでは、
MockedClass::methodA
のようにメソッド参照で指定していますが、引数がある場合の記述方法は以下のようになります。mocked.when(() -> { MockedClass.methodC(Mockito.anyString()); }) .thenReturn("stub");引数リストにはMockitoのany()を指定できます。
もちろん、引数なしの場合でもラムダ式による書き方は有効です。
ラムダ式の記述ルールに従い、{}の省略も可能。mocked.when(() -> MockedClass.methodA()) .thenReturn("stub");
- 投稿日:2020-10-14T18:16:27+09:00
Spring Cloud Gatewayを徹底解説してみる
はじめに
本記事は https://tech-blog.yoshikiohashi.dev/posts/spring-cloud-gateway-explain のクロスポスト記事になります。
この記事はGatsbyというヘッドレスCMS技術で構成されています。
Spring Cloud Gatewayとは?
一言でいうと「マイクロサービス向けのOAuth2認証API基盤」になります。
公式が親切に日本語で解説してるので見てみましょう。
このプロジェクトは、Spring MVC の上に API Gateway を構築するためのライブラリを提供します。Spring Cloud Gateway は、API にルーティングするためのシンプルでありながら効果的な方法を提供し、セキュリティ、モニタリング / メトリック、復元力などの横断的な懸念を API に提供することを目的としています。
つまり?
マイクロサービス間などでOAuth2などの認証問題を解決してくれるフレームワークになります。アプリ間のルーティングもしてくれるので認証機能を備えたAPI上のプロキシーのような存在になります。
アーキテクチャ
それぞれの役割をわかりやすくするため図で見てみましょう。
全体的なアーキテクチャ図
仮にVue.jsなどのFront AppからGatewayのURLにアクセスするとCognito(AWSの場合)などのIDMとOAuth2認証を行い、指定のResource APIと通信ができるようになります。
後述しますが、Front to Gateway間はSessionで状態管理されており、Gateway to API間はJWTの形式で認証のやり取りがされます。
なのでAPI側はJWTの認証チェックだけ行えばOKということになります。(APIはSpringがBetterではあるが別言語でもSo Good)
Workshop
この記事でWorkshopを作ってもよいのですが、大変長くなるので認証サーバのUAAを使用したこちらのRepositoryを進めると理解が深まると思います。
- https://spring.pleiades.io/guides/gs/gateway/
- https://github.com/spring-cloud-samples/sample-gateway-oauth2login
認証の手順
導入する目的・メリット
- Front, BFFにAccessTokenを持たせないための設計ができる
- OAuth2の複雑な認証フローを自分で開発したくない
- 認証に必要な設定情報を埋め込むだけで認証を行う役割をもつ
- 認証部分が独立しているため他言語APIと連携も容易なのでマイクロサービスアーキテクチャの認証部分として適している。
- 再利用が可能!!!
ちなみに
マイクロサービス関係なくAPI内に認証を入れる場合であれば、Spring OAuth2 Clientを設定しても良い
デメリット。。。
Spring Cloud GatewayというよりSpring 5のWebFluxの問題かもしれませんが、NettyというWebサーバ上でConnectionが切れる問題が多発したり(こちらの記事で解説)、RefreshTokenの自動更新処理などは自分で入れる必要があります。
つまり、既存のISSUEがあり既存問題に対して自分たちの力で解消できるどうかが導入のキーになると思います。
認証の仕組み
わかりやすく図化してみました。一般的にFrontにJWTを直接持つとセキュリティ的にグレー(?)なのでSessionを保持してクレデンシャル情報をサーバ内に内包しているためかなりセキュアであると言えます。
今回はCognito User Poolを使用していると仮定しているためAWS Resourceと疎通しています
アクセストークンの自動更新処理はしてくれないの?
Spring Cloud GatewayのFilter機能により実現できます。通信間に処理を入れ込むことができる。HTTP通信の際に有効期限を確認し、切れていれば更新を行う処理を入れることができます。
すでにCloseしてるので標準搭載されるかもしれません。
コードだとこんな感じ
private static ReactiveOAuth2AuthorizedClientManager createDefaultAuthorizedClientManager( ReactiveClientRegistrationRepository clientRegistrationRepository, ServerOAuth2AuthorizedClientRepository authorizedClientRepository) { final ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.builder() .authorizationCode() .refreshToken(configurer -> configurer.clockSkew(accessTokenExpiresSkew)) .clientCredentials(configurer -> configurer.clockSkew(accessTokenExpiresSkew)) .password(configurer -> configurer.clockSkew(accessTokenExpiresSkew)) .build(); final DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager = new DefaultReactiveOAuth2AuthorizedClientManager( clientRegistrationRepository, authorizedClientRepository); authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); return authorizedClientManager; } public GatewayFilter apply() { return apply((Object) null); } @Override public GatewayFilter apply(Object config) { return (exchange, chain) -> exchange.getPrincipal() // .log("token-relay-filter") .filter(principal -> principal instanceof OAuth2AuthenticationToken) .cast(OAuth2AuthenticationToken.class) .flatMap(this::authorizeClient) .map(OAuth2AuthorizedClient::getAccessToken) .map(token -> withBearerAuth(exchange, token)) // TODO: adjustable behavior if empty .defaultIfEmpty(exchange).flatMap(chain::filter); } private ServerWebExchange withBearerAuth(ServerWebExchange exchange, OAuth2AccessToken accessToken) { return exchange.mutate().request(r -> r.headers(headers -> headers.setBearerAuth(accessToken.getTokenValue()))).build(); } private Mono<OAuth2AuthorizedClient> authorizeClient(OAuth2AuthenticationToken oAuth2AuthenticationToken) { final String clientRegistrationId = oAuth2AuthenticationToken.getAuthorizedClientRegistrationId(); return Mono.defer(() -> authorizedClientManager.authorize(createOAuth2AuthorizeRequest(clientRegistrationId, oAuth2AuthenticationToken))); } private OAuth2AuthorizeRequest createOAuth2AuthorizeRequest(String clientRegistrationId, Authentication principal) { return OAuth2AuthorizeRequest.withClientRegistrationId(clientRegistrationId).principal(principal).build(); }どうでも良いけどMonoとかFluxの非同期処理難しいよね。
使い方
基本的に引数ゲーです。こんな感じで設定すれば後々docker-composeファイルにも適用できます。
docker-compose
spring-cloud-gateway-service: build: ./spring-cloud-gateway image: barathece91/gateway-service-k8s ports: - "9500:9500" depends_on: - jio-microservice - airtel-microservice - vodaphone-microservice environment: SPRING_PROFILES_ACTIVE: path SPRING_CLOUD_GATEWAY_ROUTES[0]_URI: http://jio-microservice:9501 SPRING_CLOUD_GATEWAY_ROUTES[0]_ID: jio-service SPRING_CLOUD_GATEWAY_ROUTES[0]_PREDICATES[0]: Path= /jio/* SPRING_CLOUD_GATEWAY_ROUTES[0]_FILTERS[0]: StripPrefix=1 SPRING_CLOUD_GATEWAY_ROUTES[1]_URI: http://airtel-microservice:9502 SPRING_CLOUD_GATEWAY_ROUTES[1]_ID: airtel-service SPRING_CLOUD_GATEWAY_ROUTES[1]_PREDICATES[0]: Path= /airtel/* SPRING_CLOUD_GATEWAY_ROUTES[1]_FILTERS[0]: StripPrefix=1 SPRING_CLOUD_GATEWAY_ROUTES[2]_URI: http://vodaphone-microservice:9503 SPRING_CLOUD_GATEWAY_ROUTES[2]_ID: vodaphone-service SPRING_CLOUD_GATEWAY_ROUTES[2]_PREDICATES[0]: Path= /vodaphone/* SPRING_CLOUD_GATEWAY_ROUTES[2]_FILTERS[0]: StripPrefix=1kubenetes
kubenetesワカラナイ...サンプルをどうぞ。
https://github.com/spring-cloud/spring-cloud-kubernetes/tree/master/spring-cloud-kubernetes-examples
関連資料
- 投稿日:2020-10-14T16:56:50+09:00
Yellowfinの高度な関数を作成する
目的
Yellowfin(BIツール)にはもともと用意された関数の他に、自分で関数を作って拡張できる「高度な関数」というものがあります。これまで使ったことがなく、プログラムを書かないといけないことから手を付けていなかったのですが、幾何平均を求めたいという要望があり、作ってみた感じを記事にしています。個人的には、正直公式のWikiだけではなかなか理解するのが難しいところもあったのでそこをフォローできたらいいなと思います。
はじめに
基本的な開発の始め方としては、Eclipseを使って開発するのですが、高度な関数の基礎を見ながら設定すれば自動的に必要なメソッドがAnalyticalFunctionからOverrideして追加されるので、その中のパラメータを設定して、必要な処理をapplyAnalyticFunctionメソッドの中で記述していく流れになります。
1つのカラムの合計を出す関数(累積合計)であれば、高度な関数の作成を見ればわかると思います。この例では選択したカラムから1レコードずつ足していくだけですね。結果的に、applyAnalyticFunctionのreturnの値がレポートの方に返されます。
公式wikiから抜粋↓
simpleAdvancedFunction.javaimport com.hof.mi.interfaces.AnalyticalFunction; public class AccumulativeTotal extends AnalyticalFunction{ private Double total = 0.0; public String getName() { return "Accumulative Total"; } public String getDescription() { return "Calculates the accumulative total for the selected field"; } public String getCategory() { return "Analysis"; } public String getColumnHeading(String colName) { return "Accumulative Total of " + colName; } public int getReturnType() { return TYPE_NUMERIC; } public boolean acceptsNativeType(int type) { return type == TYPE_NUMERIC; } public Object applyAnalyticFunction(int index, Object value) throws Exception { if (value == null) return null; this.total += Double.valueOf(value.toString()); return this.total; } }TYPE_NUMERICのような定数については高度な関数付録を参照してください。Integer値と定数とどちらでも大丈夫です。
カラム全体に対しての数値の処理について(例.幾何平均)
これは詳しくはあまり載っていなかったので手探りで試してみたのですが、高度な関数の作成にしれっと書いてあるpreAnalyticFunctionメソッドを使用します。データセット全体に渡り操作を実行するために使用されます。と書かれているんですがサンプルが書いてないのでイメージしにくいんですよね。先程のコードの追加・変更の部分のみ記載します。
解説としては、preAnalyticFunctionの引数selectedColにカラムの値のオブジェクト配列がレコード分格納されているので、初めだけthis.totalに代入してその後は何回処理を繰り返したかをcntでインクリメントしながら掛けていきます。
最終的に、Math.powで処理を繰り返した数で冪根(累乗根)したものをapplyAnalyticFunctionでそのまま返(表示)しているだけです。geometricMean.javaprivate int cnt = 0;//add public Object applyAnalyticFunction(int index, Object value) throws Exception {//modify method // TODO Auto-generated method stub if (value == null) return null; return this.total; } public void preAnalyticFunction(Object[] selectedCol){//add method this.total=0.0; for (Object value : selectedCol) { if (value!=null) { if(this.total==0.0){ this.total = Double.valueOf(value.toString()); } else { this.total= this.total * Double.valueOf(value.toString()); } cnt++; } } this.total = Math.pow( this.total, (double)1/cnt); } public Object applyAnalyticFunction(int index, Object value) throws Exception {//modify // TODO Auto-generated method stub if (value == null) return null; return this.total; }選択したカラムに対して他のカラムを比較したい・他のカラムも含めて処理したい場合(例.共分散)
高度な関数を使用するカラムの他に任意のカラムに対しての影響・比較をしたい時にユーザーにカラムを指定させて関数を実行するといったこともできるようです。
これもwikiでは分かりづらかったのですが、パラメーターセットアップのsetupParametersのaddParameterメソッドを通してパラメーターに加えることでgetParameterValue("ユニークキー")でその設定したパラメーターが取得できます。この時にparameterのsetDataTypeをTYPE_FIELD(100)にすることで任意のカラムを選択できるようになります。
コードの解説ですが、これも一番初めのwikiのコードからの変更点のみ記載します。
preAnalyticFunctionで高度な関数を適用した値(selectedCol)のオブジェクト配列と、早速getParameterValueで先程解説したユニークキー"FIELD_SELECTION"のカラムの値(inputColumn)のオブジェクト配列を取得し(上の画像では要素1)、それぞれDouble型にキャストして配列に加えていきます。そこでこの2つの配列をcovarianceに渡して共分散の値を取得しています。
covarianceとsumメソッドについては共分散と合計を求めるだけなので解説を割愛します。covariance.javaimport java.util.ArrayList;//add import java.util.List;//add private List<Double> items1 = new ArrayList<>();//add private List<Double> items2 = new ArrayList<>();//add protected void setupParameters() {//add Parameter p = new Parameter(); p.setUniqueKey("FIELD_SELECTION"); p.setDisplayName("Column"); p.setDescription("Compare this numeric field to the selected field"); p.setDataType(TYPE_FIELD);//100 p.setAcceptsFieldType(TYPE_NUMERIC, true); p.setDisplayType(DISPLAY_SELECT);//6 addParameter(p); } public void preAnalyticFunction(Object[] selectedCol){//add this.total=0.0; Object [] inputColumn = (Object[]) getParameterValue("FIELD_SELECTION"); for (Object value : selectedCol) { items1.add(Double.valueOf(value.toString())); } for (Object value : inputColumn) { items2.add(Double.valueOf(value.toString())); } Double r = covariance(items1, items2); this.total = r; } public Object applyAnalyticFunction(int index, Object value) throws Exception {//modify // TODO Auto-generated method stub if (value == null) return null; return this.total; } public Double covariance(final List<Double> items1, final List<Double> items2) {//add List<Double> items3 = new ArrayList<>(); int n = items1.size(); Double i1Mean = sum(items1)/n; Double i2Mean = sum(items2)/n; for (int i = 0; i < n; i++) { items3.add((i1Mean - items1.get(i)) * (i2Mean - items2.get(i))); } Double i3Sum = sum(items3); return i3Sum / n; } public Double sum(final List<Double> items) {//add Double result = 0.0; for (Double item : items) { result += item; } return result; }ということで
少し説明が長くなってしまいましたが、高度な関数を作成することでレポートで表現するには少しめんどくさいこと(多重なreportFromReportなど)をJavaで書ける範囲ですぐに値を出すことができるようになり、コーディングの手間はかかりますが、いつも社内で使っているある一定の式を当てたい・2つのカラムの関係性を求めたいなどの処理を簡単にてきようできるようになりますね。
- 投稿日:2020-10-14T15:16:57+09:00
クラスとインスタンス 初心者向け Java
環境
・MacBookpro
・IntelliJ IDEA CEJava学習中アウトプットのため投稿します。
クラスとインスタンスについて
クラス・・・設計書、骨組み
インスタンス・・・クラスを元に作られた実体あなたは上司から大量生産猫型ロボット工場から、人間界世話型ロボットとその妹ロボットを1体ずつ作るよう指示を受けました。
上司から詳細データが渡されました。
人間界世話型ロボットについて 名前:ドラ絵もん 色:青 性別:男 ドラ絵もんの妹ロボットについて 名前:ドラ美ちゃん 色:ピンク 性別:女 以上2体を作成するように。詳細データを見ると3要素が必要です。
・名前(name)
・色(color)
・性別(sex)それではまず上司の指示通りのクラスを完成させます。
Robot.javaclass Dora { String name; String color; String sex; Robot(String name, String color , String sex){ this.name = name; this.color = color; this.sex = sex; } String sayBox(){ return name +"です。色は、"+color+"です。性別は"+sex+"です。"; } }これでクラスが作れました。
次はインスタンス(詳細)を作ります。
RobotFactory.javapublic class RobotFactory { public static void main(String[] args){ Dora robota = new Dora("ドラ絵もん","青","男"); System.out.println(robota.sayBox()); Dora roboco = new Dora("ドラ実ちゃん","ピンク","女"); System.out.println(roboco.sayBox()); } }成功です。
- 投稿日:2020-10-14T09:04:11+09:00
javaでwebApi備忘録
事前準備
・エクリプスの最新版
①eclipsのインストール方法
https://snome.jp/framework/springboot-install-win/
SpringBootの使用の前までで完了
ワークスペースのなぞ?
Eclipse はワークスペースとして指定されてディレクトリに “.metadata” という隠しディレクトリを作るので、これを検索します。
②Javaコンパイラの設定
特に設定しなくてもOK
③WEBAPIの作成方法
サンプルを元にマーベンのPOMを変更
https://codezine.jp/article/detail/11380?p=3
④Java Spring Boot JSONの送信と受信のサンプル
https://itsakura.com/java-springboot-json#s7
ハローワールドの出し方
https://qiita.com/kuro227/items/d7da647e9f3be78a5f92
アクセスするURL
http://localhost:8081/hello/
リファレンスAPI スプリング
https://spring.pleiades.io/guides/tutorials/bookmarks/
マーベンのPOM
https://mvnrepository.com/search?q=javax.validation
spring boot で web api サーバを作る
https://qiita.com/kuro227/items/d7da647e9f3be78a5f92
修正点
・HelloWorldController.java
・Syain.javaファイル(test.html)
```javaでマッピング
@RequestMapping(method = RequestMethod.GET)
public String getHello() {
return "hello world!";
}
file:///C:/Users/shimizu/Desktop/test2.html
でここにマッピングされる
@RequestMapping(value = "/index",method = {RequestMethod.POST})
@ResponseBody
public Syain output1(
@RequestBody Syain syain) {
System.out.println(syain.getBangou());
System.out.println(syain.getName());
return syain;
}
- 投稿日:2020-10-14T08:22:58+09:00
Docker, java, vscodeの開発環境を構築
前回の記事
初心者がdocker for macをインストールしてphp7.0の動作環境を用意する
この記事
前回はphp7.0の動作環境を用意しました。
今回はjavaを用意してみます。
目標はdocker + java + vscode。参考記事
Visual Studio CodeのRemote DevelopmentとDockerで快適な開発環境をゲット
Remote Development
Remote Developmentの拡張機能をインストール
Microsoftが公開してるテストプロジェクトのクローンを用意
gitgit clone https://github.com/Microsoft/vscode-remote-try-javaテストプロジェクトを開く
Visual Studio Codeの左下の緑のボタンをクリック>Remote-Containers: Open Folder in Container>gitのクローンフォルダを選択
dockerでコンテナが起動しているか確認
ビルドしてプロジェクトが動作するか確認
まとめ
前回の記事の方法よりRemote Developmentを使用するのが簡単ですね。
javaのバージョンも選択できるみたい。
ありがとう、Microsoft!!