20210426のJavaに関する記事は11件です。

[Java]特定の半角文字を全角文字に変換

半角記号を全角記号に変換してくれという依頼を受けることがあるため、メモとして残します。 方法① 半角文字と全角文字の文字コードの差分を加算する 英数字と一部の記号は全角と半角でコードの並び順が一致しているため、全角と半角の差分の65248を加算することで全角にすることができる。 sample.java public class Sample { // 全角にする文字 private static final Character[] SYMBOLS = { 'a', 'b', 'c', '1', '2', '3', '&', '_', '-' }; public static void main(String[] args) { String halfWidth = "a1_"; String fullWidth = "a1_"; // 文字コードの差分が65248になるか確認 for (int i = 0;i < halfWidth.length();i++) { System.out.println(fullWidth.charAt(i) - halfWidth.charAt(i)); // 65248 }; String target = "abc&123_def-456"; System.out.println(toFullWidth(target)); // abc&123_def-456 } private static String toFullWidth(String target) { char[] charArray = target.toCharArray(); StringBuilder sb = new StringBuilder(); for (char c : charArray) { if (Arrays.asList(SYMBOLS).contains(c)) { // 半角の文字コードに65248を加算して全角に変換 sb.append((char)(c + 65248)); } else { sb.append(c); } } return sb.toString(); } } 方法② 全角にする文字をMapで定義しておく sample.java public class Sample { // 全角にしたい文字 private static final Map<String, String> SYMBOL_MAP; static { Map<String, String> map = new HashMap<>(); map.put("a", "a"); map.put("b", "b"); map.put("c", "c"); map.put("1", "1"); map.put("2", "2"); map.put("3", "3"); map.put("&", "&"); map.put("_", "_"); map.put("-", "-"); // 変更不可のマップを生成 SYMBOL_MAP = Collections.unmodifiableMap(map); } public static void main(String[] args) { String target = "abc&123_def-456"; System.out.println(toFullWidth(target)); // abc&123_def-456 } private static String toFullWidth(String target) { String[] strArray = target.split(""); StringBuilder sb = new StringBuilder(); for (String s : strArray) { if (SYMBOL_MAP.containsKey(s)) { // Mapから全角文字を取得 sb.append(SYMBOL_MAP.get(s)); } else { sb.append(s); } } return sb.toString(); }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Azure Spring Cloudでブルーグリーンデプロイメントする

ブルーグリーンデプロイメントの話は勉強会で何回も聴いたことがあるのですが、実際に自分で手を動かして試したことはありませんでした。設定が大変そうだよなぁ、と思っていたのですが、Azure Spring Cloudでめちゃくちゃ簡単にできてびっくりしました。 手順はほぼこちらのドキュメントどおりです。 まずはAzure Spring Cloudインスタンスを作成します。画面でポチポチと作りました。間違えてBasicレベルを選択してしまったのですが、ブルーグリーンデプロイメントを試すにはStandardレベルが必要なのでご注意ください。(後から変えることは可能です) インスタンスの準備はこれだけです。 続いてデプロイするアプリケーションを作成します。環境はDocker上のDebianで作りました。Azure CLIのAzure Spring Cloud 拡張機能が必要なため、以下の3行目で追加しています。 FROM maven:3.8.1-openjdk-11 RUN curl -sL https://aka.ms/InstallAzureCLIDeb | bash RUN az extension add --name spring-cloud VSCodeの拡張機能「Spring Initializr Java Support」でspring-boot-starter-webだけ追加したプロジェクトを作ります。 helloを表示するだけの簡単なアプリケーションです。 package com.example.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.GetMapping; @SpringBootApplication @RestController public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } @GetMapping("/hello") public String hello() { return "hello"; } } ビルドします。 mvn clean packge -DskipTests Azureにログインします。 az login インスタンス上にdemoというアプリ(の雛形?)を作ります。 az spring-cloud app create -n demo -g springcloudrg -s kikutarospringcloud --assign-endpoint デプロイします。 az spring-cloud app deploy -n demo -g springcloudrg -s kikutarospringcloud --jar-path target\demo-0.0.1-SNAPSHOT.jar アプリのURLが表示されているので、末尾に /hello をつけてアクセスします。 helloが表示されました。 続いて、更新したアプリケーション(グリーン)を作成してデプロイします。コードは「hello」を「hello new release!」に変えただけです。 @GetMapping("/hello") public String hello() { return "hello new release!"; } ビルドします。 mvn clean packge -DskipTests グリーンデプロイ(と呼ぶらしい)します。 az spring-cloud app deployment create -n green --app demo -g springcloudrg -s kikutarospringcloud --jar-path target\demo-0.0.1-SNAPSHOT.jar 私は最初以下のエラーになりました。 Basic sku only allows 1 deployment under one app. これは最初に書いた通り、プランがBasicでブルーグリーンデプロイメントに対応していないためです。画面からStandardプランにアップグレードして、再度デプロイしたら成功しました。 デプロイメニューを見ると、defaultとgreenの2つが並んでいます。 あとはgreenの方のメニューから「運用環境として設定」を選べば、こちらに切り替わります。 切り替えて再度アクセスすると、ちゃんと「new release!」がついた新しいアプリケーションに切り替わっています。 こんなに簡単にブルーグリーンデプロイメントができるんだ...!と地味に感動しました。(すごい今更ですが...) Spring Cloudの話はかなり前にJJUGの勉強会で聴いていましたが、当時はなんとなく敷居が高そうだなぁ...と思って触っていませんでした。 今回、Azure Spring Cloudを触ってみましたが、すごく簡単でした。Spring Cloudについて改めて勉強したいなーと思いました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Dockerコンテナ起動時に環境変数をquarkusのapplication.propertiesに渡す方法

はじめに quarkusでは以下のようにapplication.propertiesに設定した値を変数に代入することができます。 https://quarkus.io/guides/config 開発環境、ステージング環境、本番環境など異なる環境によって変数の値が異なる場合以下のように記載することもできますが、 ビルドした成果物を管理する際は環境ごとに管理しなければいけません。 application.properties quarkus.http.port=9090 %dev.quarkus.http.port=8181 %staging.quarkus.http.port=9999 ちょうど業務で成果物をDockerイメージで管理することがあり、環境ごとに成果物が異なるのは管理の面で嫌だなぁと思っておりました。 そこで本記事では、環境によって変数の値が異なる場合でも、管理するDockerイメージを一つで済ませる方法を記載します。 ※サンプルのプロジェクトを修正し説明します 準備 以下のコマンドでサンプルプロジェクトを作成します。 $ mvn io.quarkus:quarkus-maven-plugin:1.13.2.Final:create \ -DprojectGroupId=org.acme \ -DprojectArtifactId=config-quickstart \ -DclassName="org.acme.config.GreetingResource" \ -Dpath="/greeting" $ cd config-quickstart ディレクトリ構造は以下のようになっていると思います。 . ├── mvnw ├── mvnw.cmd ├── pom.xml ├── README.md └── src ├── main │   ├── docker │   │   ├── Dockerfile.jvm │   │   ├── Dockerfile.legacy-jar │   │   ├── Dockerfile.native │   │   └── Dockerfile.native-distroless │   ├── java │   │   └── org │   │   └── acme │   │   └── config │   │   └── GreetingResource.java │   └── resources │   ├── application.properties │   └── META-INF │   └── resources │   └── index.html └── test └── java └── org └── acme └── config ├── GreetingResourceTest.java └── NativeGreetingResourceIT.java application.properties修正 現段階では何も記載されていないので、以下を記載します。 src/main/resources/application.properties greeting.message = hello greeting.name = ${NAME:quarkus} # ${環境変数名:デフォルト値} ソース修正 以下のように修正します src/main/java/org/acme/config/GreetingResource.java package org.acme.config; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import org.eclipse.microprofile.config.inject.ConfigProperty; //★追加 @Path("/greeting") public class GreetingResource { @ConfigProperty(name = "greeting.message") //★追加 String message; //★追加 @ConfigProperty(name = "greeting.name") //★追加 String name; //★追加 @GET @Produces(MediaType.TEXT_PLAIN) public String hello() { return message + " " + name; //★修正 } } 修正したソースコードをbuild 以下のコマンドを実行するとquarkus-run.jarというファイルが作成されます。(testはskipしてます) $ mvn package -Pnative -Dnative-image.docker-build=true -Dmaven.test.skip=true $ ls target/quarkus-app/ app lib quarkus quarkus-app-dependencies.txt quarkus-run.jar Dockerコンテナイメージ作成 以下のコマンドでDockerのコンテナイメージを作成します。 $ docker build -f src/main/docker/Dockerfile.jvm -t quarkus/greeting $ docker image ls | grep greeting quarkus/greeting latest 10466a00a3a4 2 hours ago 330MB 作成されてますね。 環境変数を設定して作成したイメージを起動 docker-composeを使用し、イメージを起動したいと思います。 以下のyamlを作成し、application.propertiesに記載した環境変数を設定します。 docker-compose.yaml version: '3.3' services: quarkus-greeting: image: quarkus/greeting:latest container_name: quarkus-greeting environment: - NAME=docker ports: - 8080:8080 以下のコマンドでイメージを起動し、起動確認します。 $ docker-compose up -d $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 9a93d018db6e quarkus/greeting:latest "/deployments/run-ja…" About a minute ago Up About a minute 0.0.0.0:8080->8080/tcp quarkus-greeting 念のため環境変数が設定されているか確認 $ docker exec -it 9a93d018db6e env | grep NAME=docker NAME=docker アプリケーションの動作確認 curlコマンドで起動したイメージに対してリクエストを投げてみます $ curl localhost:8080/greeting hello docker 意図した値が入ってますね。 ちなみに、 環境変数を設定しない場合はapplication.propertiesで設定したデフォルト値が使用され、以下のような結果になります。 $ curl localhost:8080/greeting hello quarkus このように、環境ごとに変数の値が異なる場合でもイメージは1つで済みそうですね。 今回はdocker-composeでしたがkubernetesの場合でもmanifestfileに環境変数を設定することで同様のことができます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Selenium ボタン名でクリック

1.ボタン名でクリック CssSelectorを使用すると、forループ等をせずにエレメントを見つけられます。 HTML.html <input type="submit" value="登録" /> C#.cs driver.FindElement(By.CssSelector("input[value='登録']")).Click(); または driver.FindElement(By.CssSelector("input[value='登録']")).Submit(); Java.java driver.findElement(By.cssSelector("input[value='登録']")).click(); または driver.findElement(By.cssSelector("input[value='登録']")).submit(); Python.py driver.find_element_by_css_selector("input[value='登録']").click() または driver.find_element_by_css_selector("input[value='登録']").submit()
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Selenium ボタンの表示文字列でクリック

1.ボタン名(ボタンの表示文字列)でクリック CssSelectorを使用すると、forループ等をせずに表示文字列等でエレメントを見つけられます。 HTML.html <input type="submit" value="登録" /> C#.cs driver.FindElement(By.CssSelector("input[value='登録']")).Click(); または driver.FindElement(By.CssSelector("input[value='登録']")).Submit(); Java.java driver.findElement(By.cssSelector("input[value='登録']")).click(); または driver.findElement(By.cssSelector("input[value='登録']")).submit(); Python.py driver.find_element_by_css_selector("input[value='登録']").click() または driver.find_element_by_css_selector("input[value='登録']").submit() 記述方法は異なりますが、XPathで同じことができます。 C#.cs driver.FindElement(By.XPath("//input[@value='登録']")).Click(); または driver.FindElement(By.XPath("//input[@value='登録']")).Submit(); Java.java driver.findElement(By.xpath("//input[@value='登録']")).click(); または driver.findElement(By.xpath("//input[@value='登録']")).submit(); Python.py driver.find_element_by_xpath("//input[@value='登録']").click() または driver.find_element_by_xpath("//input[@value='登録']").submit() XPathの参考:Seleniumで要素を選択する方法まとめ - Qiita
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

プログラミング初心者が基礎基本を地道に身に着ける日記(Java_ABC199編)

※本稿はAtCoderProblemsのBeginnerContest,A~C問題レベルをJavaを用いて地道に解いていく人間の観察日記です 目次 まえがき AtCoder Beginner Contest 199に挑戦 A問題 B問題 C問題 感想 お世話になったサイト等 まえがき  お久しぶりです。E2veeです。実は就活というものに足を取られており、更新が長らく途絶えていました。こうして記事...もとい日記を書けるのも、無事に就活を終えることができたからです。知人・友人並びに、大学の先生方に感謝します。身分不相応と言えるくらい凄い企業さんから内々定を頂けるみたいなので、あとは研究とプログラミングと(>>>趣味<<<)を充実させたいなと。  先日初めてのABCにライブ参加しました。C問題まで解ければナイス!と思って参加しましたが、結果はBまでしか解けず、Java特有の参照渡しもとい参照の値渡しなどで混乱したり、純粋にアイデアが凡庸なものだったりと...学びが多い結果となりました。次回も参加したいと思ってます。C問題を安定させるぞ!!(`・ω・´) AtCoder Begginer Contest 199に挑戦 A問題 199A.java import java.util.*; public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); int a = sc.nextInt(); int b = sc.nextInt(); int c = sc.nextInt(); int calc_left = a*a + b*b; int calc_right = c*c; if(calc_left < calc_right){ System.out.println("Yes"); }else{ System.out.println("No"); } } }  指の準備体操~~っ。はじめっ!  おわり! B問題 199B.java import java.util.*; public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); int n = sc.nextInt(); int[] a = new int[n]; int[] b = new int[n]; int a_max = 0; for(int i=0; i<n; i++){ a[i] = sc.nextInt(); if(a_max < a[i]) a_max = a[i]; } int b_min = 1000; for(int i=0; i<n; i++){ b[i] = sc.nextInt(); if(b[i] < b_min) b_min = b[i]; } int calc = b_min - a_max; if(calc < 0){ System.out.println("0"); }else{ System.out.println(calc+1); } } }  5分くらい問題を眺めてたら最小の区間の取り方は単純じゃんとなりまして...。はい。ここまでで13分でした。...そう。のこりの80分強でC問題が解けなかったのです...。残念無念...。 C問題 199C.java //AC 実行時間1212ms import java.util.*; public class Main { public static void swap(char[] x, int xi, char[] y, int yi){ char temp = x[xi]; x[xi] = y[yi]; y[yi] = temp; return; } public static void main(String[] args) { Scanner sc = new Scanner(System.in); int n = sc.nextInt(); String s = sc.next(); int q = sc.nextInt(); int[] t = new int[q]; int[] a = new int[q]; int[] b = new int[q]; for(int i=0; i<q; i++){ t[i] = sc.nextInt(); a[i] = sc.nextInt(); b[i] = sc.nextInt(); } char[] s_array = s.toCharArray(); char[] front = new char[n]; char[] back = new char[n]; for(int i=0; i<n+n; i++){ if(i<n) front[i] = s_array[i]; else back[i-n] = s_array[i]; } for(int i=0; i<q; i++){ int ap = a[i]-1; int bp = b[i]-1; if(t[i]==1){ if(ap < n && bp < n){ swap(front,ap, front,bp); }else if(ap < n && n <= bp){ swap(front,ap, back,bp-n); }else if(n <= ap && bp < n){ swap(back,ap-n, front,bp); }else{ swap(back,ap-n, back,bp-n); } }else{ char[] temp = front; front = back; back = temp; } } String ans = String.valueOf(front) + String.valueOf(back); System.out.println(ans); } }  普通に一つの配列を弄ると2000msを超えてしまってTLEになってしまったんですよね。なので、予め前後(front, back)で配列を分けておいて、それぞれに適したswapを実装するという形で落ち着きました。ちょっとの工夫で実行時間がかなり短縮されるこの感覚。ハマってしまう理由が若干分かったのかもしれません。以下はTLEとなった例です。 199C_TLE.java //TLE 実行時間2208ms import java.util.*; public class Main { public static char[] swap(String mystring, int x, int y){ char ch[] = mystring.toCharArray(); char tempo = ch[x]; ch[x] = ch[y]; ch[y] = tempo; return ch; } public static String swap2(String mystring, int x){ String front = mystring.substring(0, x); String back = mystring.substring(x); return back+front; } public static void main(String[] args) { Scanner sc = new Scanner(System.in); int n = sc.nextInt(); String s = sc.next(); int q = sc.nextInt(); int[] t = new int[q]; int[] a = new int[q]; int[] b = new int[q]; for(int i=0; i<q; i++){ t[i] = sc.nextInt(); a[i] = sc.nextInt(); b[i] = sc.nextInt(); if(t[i] == 1){ s = String.valueOf(swap(s, a[i]-1, b[i]-1)); }else if(t[i] == 2){ s = swap2(s, n); } } System.out.println(s); } }  どうやらJavaのsubstring()の実行時間はO(n)らしく、結局for文を二周してることと変わりないということだそう。また、Javaの有名な問題(?)である、参照の値渡し・値渡しはできても、C++のような参照渡しはできないという点があまり理解できていなかった点も相まって、かなり時間を浪費してしまいました。...無念。線形リストを使えばそれっぽいことができそうですが、元気が無かったので次回に...。 _________________ ※追記※  どうやらchar[] front, back;をクラス変数にしておけばfbswap()が実装できそうです。 199C_2.java import java.util.*; public class Main { static char[] front, back; public static void fbswap(){ char[] tmp=front; front=back; back=tmp; } public static void swap(char[] x, int xi, char[] y, int yi){ char temp = x[xi]; x[xi] = y[yi]; y[yi] = temp; return; } public static void main(String[] args) { Scanner sc = new Scanner(System.in); int n = sc.nextInt(); String s = sc.next(); int q = sc.nextInt(); int[] t = new int[q]; int[] a = new int[q]; int[] b = new int[q]; for(int i=0; i<q; i++){ t[i] = sc.nextInt(); a[i] = sc.nextInt(); b[i] = sc.nextInt(); } char[] s_array = s.toCharArray(); front = new char[n]; back = new char[n]; for(int i=0; i<n+n; i++){ if(i<n) front[i] = s_array[i]; else back[i-n] = s_array[i]; } for(int i=0; i<q; i++){ int ap = a[i]-1; int bp = b[i]-1; if(t[i]==1){ if(ap < n && bp < n){ swap(front,ap, front,bp); }else if(ap < n && n <= bp){ swap(front,ap, back,bp-n); }else if(n <= ap && bp < n){ swap(back,ap-n, front,bp); }else{ swap(back,ap-n, back,bp-n); } }else{ fbswap(); } } String ans = String.valueOf(front) + String.valueOf(back); System.out.println(ans); } }  加えて、swap()に関しても、C++に近い感じで使えそう。結構似せることができるもんですね。ちなみに実行時間は1139msでした。ほんのちょっと早くなったかな? 感想  ブランクが空いていたという?な言い訳をしつつも、次回も参加してみたいと思います。とりあえず流れとしては、 1. ABCにライブで参加する 2. 茶色(C問題)程度まで復習を行う 3. 過去問を一回分、茶色(C問題)程度まで行う 4. 最初に戻る  を繰り返していきたいと思います。更新は週一程度でできればなと思っています。最後に色々質問・相談に答えてくれた水色コーダー君に感謝。C++は便利だね...。 お世話になったサイト ☆特別枠☆ 水色コーダー君
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Azure Functions for Java でリトライを実装する

Azure Functions for Java でリトライを実装する 未だプレビューなのですが、Azure Functions にはリトライが実装されています。 Azure Functions のエラー処理と再試行のガイダンス | Microsoft Docs 上記を参照するとC#は属性ベースでリトライが指定できるのですが、Java等のその他の言語は、function.jsonに指定しろと書いてあります。とはいえ、function.json は、自動生成されたりするものですから、使い勝手がよくありません。 調べてみると、GitHubにIsssueが立っていて動きがなかったので、質問してみると既に実装済でした。以下のタグに説明があります(SNAPSHOTですが既にリリース済です) リトライには、固定秒(FixedDelayRetry)でのものと、指数関数的バックオフ(ExponentialBackoffRetry)­‐ リトライ毎に間隔が増加していくものの、よくある2つが用意されています。 FixedDelayRetry サンプルを引用しますが、FixedDelayRetry アノテーションで、回数とインターバルを指定します。インターバルが文字列なのが行けてませんが、単に function.json への定義用かと思えばそんなものなのでしょうか。 @FunctionName("HttpExampleRetry") @FixedDelayRetry(maxRetryCount = 3, delayInterval = "00:00:05") // ★ public HttpResponseMessage runRetry( @HttpTrigger( name = "req", methods = {HttpMethod.GET, HttpMethod.POST}, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<String>> request, final ExecutionContext context) throws Exception { context.getLogger().info("Java HTTP trigger processed a request."); // Parse query parameter final String query = request.getQueryParameters().get("name"); final String name = request.getBody().orElse(query); if (name == null) { return request.createResponseBuilder(HttpStatus.BAD_REQUEST).body("Please pass a name on the query string or in the request body").build(); } else { return request.createResponseBuilder(HttpStatus.OK).body(name).build(); } } ExponentialBackoffRetry こちらのサンプルからの引用。 ExponentialBackoffRetry アノテーションで、回数、最小インターバル、最大インターバルを指定します。あとは適当にフレームワーク側がリトライしてくれるでしょう。 @FunctionName("HttpExampleExponentialBackoffRetry") @ExponentialBackoffRetry(maxRetryCount = 3, minimumInterval = "00:00:01", maximumInterval = "00:00:03") // ★ public HttpResponseMessage runRetryExponentialBackoffRetry( @HttpTrigger( name = "req", methods = {HttpMethod.GET, HttpMethod.POST}, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<String>> request, final ExecutionContext context) throws Exception { context.getLogger().info("Java HTTP trigger processed a request."); // Parse query parameter final String query = request.getQueryParameters().get("name"); final String name = request.getBody().orElse(query); if (name == null) { return request.createResponseBuilder(HttpStatus.BAD_REQUEST).body("Please pass a name on the query string or in the request body").build(); } else { return request.createResponseBuilder(HttpStatus.OK).body(name).build(); } } 試してみる 開発環境で試してみましょう。各種ログをTraceレベルにして、適当にスタックトレースをはしょってあります。 以下は、リトライなしの場合。例外が出ると、そおまま終わってしまします。 [2021-04-26T08:51:37.318Z] Request successfully matched the route with name 'noretry' and template 'api/noretry' [2021-04-26T08:51:37.320Z] Executing 'Functions.noretry' (Reason='This function was programmatically called via the host APIs.', Id=f0d01936-4b9a-42e8-a7fc-2bc075e11420) [2021-04-26T08:51:37.321Z] Sending invocation id:f0d01936-4b9a-42e8-a7fc-2bc075e11420 [2021-04-26T08:51:37.323Z] Posting invocation id:f0d01936-4b9a-42e8-a7fc-2bc075e11420 on workerId:760295b9-ab89-4fbf-84bc-b9e1ac57e8ae [2021-04-26T08:51:37.325Z] HttpContext has ClaimsPrincipal; parsing to gRPC. [2021-04-26T08:51:37.327Z] Writing invocation request invocationId: f0d01936-4b9a-42e8-a7fc-2bc075e11420 to workerId: 760295b9-ab89-4fbf-84bc-b9e1ac57e8ae [2021-04-26T08:51:37.331Z] Java HTTP trigger processed a request. [2021-04-26T08:51:37.333Z] Received invocation response for invocationId: f0d01936-4b9a-42e8-a7fc-2bc075e11420 from workerId: 760295b9-ab89-4fbf-84bc-b9e1ac57e8ae [2021-04-26T08:51:37.334Z] InvocationResponse received for invocation id: f0d01936-4b9a-42e8-a7fc-2bc075e11420 [2021-04-26T08:51:37.337Z] Executed 'Functions.noretry' (Failed, Id=f0d01936-4b9a-42e8-a7fc-2bc075e11420, Duration=17ms) [2021-04-26T08:51:37.338Z] System.Private.CoreLib: Exception while executing function: Functions.noretry. System.Private.CoreLib: Result: Failure Exception: InvalidParameterException: Stack: java.lang.reflect.InvocationTargetException [2021-04-26T08:51:37.339Z] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ... [2021-04-26T08:51:37.353Z] at java.base/java.lang.Thread.run(Thread.java:834) [2021-04-26T08:51:37.354Z] Caused by: java.security.InvalidParameterException [2021-04-26T08:51:37.355Z] at com.example.moris.Function.run2(Function.java:45) [2021-04-26T08:51:37.357Z] ... 16 more [2021-04-26T08:51:37.358Z] . [2021-04-26T08:51:37.360Z] Executed HTTP request: { [2021-04-26T08:51:37.361Z] requestId: "39285172-f05f-4312-902c-e00094813005", [2021-04-26T08:51:37.362Z] identities: "", [2021-04-26T08:51:37.363Z] status: "500", [2021-04-26T08:51:37.364Z] duration: "51" [2021-04-26T08:51:37.365Z] } リトライを設定した場合は、以下の通り。リトライを3回に指定してあるのですが、Traceレベルですと、Waiting for00:00:02before retrying function execution. Next attempt: '1'. Max retry count: '3' が 確認できまました。適切に設定すれば、このログだけうまく出力させておけて、リトライがかかったことを後から確認出来る気がします。 [2021-04-26T08:53:39.116Z] Request successfully matched the route with name 'fixeddelayretry' and template 'api/fixeddelayretry' [2021-04-26T08:53:39.120Z] Executing 'Functions.fixeddelayretry' (Reason='This function was programmatically called via the host APIs.', Id=d8ef45ce-9669-4eab-9810-0b2c499217ba) [2021-04-26T08:53:39.122Z] Sending invocation id:d8ef45ce-9669-4eab-9810-0b2c499217ba [2021-04-26T08:53:39.123Z] Posting invocation id:d8ef45ce-9669-4eab-9810-0b2c499217ba on workerId:760295b9-ab89-4fbf-84bc-b9e1ac57e8ae [2021-04-26T08:53:39.125Z] HttpContext has ClaimsPrincipal; parsing to gRPC. [2021-04-26T08:53:39.127Z] Writing invocation request invocationId: d8ef45ce-9669-4eab-9810-0b2c499217ba to workerId: 760295b9-ab89-4fbf-84bc-b9e1ac57e8ae [2021-04-26T08:53:39.131Z] Java HTTP trigger processed a request. [2021-04-26T08:53:39.133Z] Received invocation response for invocationId: d8ef45ce-9669-4eab-9810-0b2c499217ba from workerId: 760295b9-ab89-4fbf-84bc-b9e1ac57e8ae [2021-04-26T08:53:39.135Z] InvocationResponse received for invocation id: d8ef45ce-9669-4eab-9810-0b2c499217ba [2021-04-26T08:53:39.138Z] Executed 'Functions.fixeddelayretry' (Failed, Id=d8ef45ce-9669-4eab-9810-0b2c499217ba, Duration=19ms) [2021-04-26T08:53:39.139Z] System.Private.CoreLib: Exception while executing function: Functions.fixeddelayretry. System.Private.CoreLib: Result: Failure Exception: InvalidParameterException: Stack: java.lang.reflect.InvocationTargetException [2021-04-26T08:53:39.141Z] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ... [2021-04-26T08:53:39.157Z] Caused by: java.security.InvalidParameterException [2021-04-26T08:53:39.158Z] at com.example.moris.Function.run(Function.java:30) [2021-04-26T08:53:39.159Z] ... 16 more [2021-04-26T08:53:39.160Z] . [2021-04-26T08:53:39.169Z] Waiting for `00:00:02` before retrying function execution. Next attempt: '1'. Max retry count: '3' [2021-04-26T08:53:41.200Z] Executing 'Functions.fixeddelayretry' (Reason='This function was programmatically called via the host APIs.', Id=b8ba3618-ed2b-4931-ba05-6454aa5da158) [2021-04-26T08:53:41.203Z] Sending invocation id:b8ba3618-ed2b-4931-ba05-6454aa5da158 [2021-04-26T08:53:41.205Z] Posting invocation id:b8ba3618-ed2b-4931-ba05-6454aa5da158 on workerId:760295b9-ab89-4fbf-84bc-b9e1ac57e8ae [2021-04-26T08:53:41.207Z] HttpContext has ClaimsPrincipal; parsing to gRPC. [2021-04-26T08:53:41.209Z] Writing invocation request invocationId: b8ba3618-ed2b-4931-ba05-6454aa5da158 to workerId: 760295b9-ab89-4fbf-84bc-b9e1ac57e8ae [2021-04-26T08:53:41.215Z] Java HTTP trigger processed a request. [2021-04-26T08:53:41.219Z] Received invocation response for invocationId: b8ba3618-ed2b-4931-ba05-6454aa5da158 from workerId: 760295b9-ab89-4fbf-84bc-b9e1ac57e8ae [2021-04-26T08:53:41.221Z] InvocationResponse received for invocation id: b8ba3618-ed2b-4931-ba05-6454aa5da158 [2021-04-26T08:53:41.226Z] Executed 'Functions.fixeddelayretry' (Failed, Id=b8ba3618-ed2b-4931-ba05-6454aa5da158, Duration=25ms) [2021-04-26T08:53:41.228Z] System.Private.CoreLib: Exception while executing function: Functions.fixeddelayretry. System.Private.CoreLib: Result: Failure Exception: InvalidParameterException: Stack: java.lang.reflect.InvocationTargetException [2021-04-26T08:53:41.230Z] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ... [2021-04-26T08:53:41.249Z] Caused by: java.security.InvalidParameterException [2021-04-26T08:53:41.250Z] at com.example.moris.Function.run(Function.java:30) [2021-04-26T08:53:41.251Z] ... 16 more [2021-04-26T08:53:41.252Z] . [2021-04-26T08:53:41.256Z] Waiting for `00:00:02` before retrying function execution. Next attempt: '2'. Max retry count: '3' [2021-04-26T08:53:43.263Z] Executing 'Functions.fixeddelayretry' (Reason='This function was programmatically called via the host APIs.', Id=68731e8d-015f-4ca9-8689-6e6953700296) [2021-04-26T08:53:43.266Z] Sending invocation id:68731e8d-015f-4ca9-8689-6e6953700296 [2021-04-26T08:53:43.268Z] Posting invocation id:68731e8d-015f-4ca9-8689-6e6953700296 on workerId:760295b9-ab89-4fbf-84bc-b9e1ac57e8ae [2021-04-26T08:53:43.269Z] HttpContext has ClaimsPrincipal; parsing to gRPC. [2021-04-26T08:53:43.271Z] Writing invocation request invocationId: 68731e8d-015f-4ca9-8689-6e6953700296 to workerId: 760295b9-ab89-4fbf-84bc-b9e1ac57e8ae [2021-04-26T08:53:43.275Z] Java HTTP trigger processed a request. [2021-04-26T08:53:43.278Z] Received invocation response for invocationId: 68731e8d-015f-4ca9-8689-6e6953700296 from workerId: 760295b9-ab89-4fbf-84bc-b9e1ac57e8ae [2021-04-26T08:53:43.280Z] InvocationResponse received for invocation id: 68731e8d-015f-4ca9-8689-6e6953700296 [2021-04-26T08:53:43.284Z] Executed 'Functions.fixeddelayretry' (Failed, Id=68731e8d-015f-4ca9-8689-6e6953700296, Duration=20ms) [2021-04-26T08:53:43.285Z] System.Private.CoreLib: Exception while executing function: Functions.fixeddelayretry. System.Private.CoreLib: Result: Failure Exception: InvalidParameterException: Stack: java.lang.reflect.InvocationTargetException [2021-04-26T08:53:43.287Z] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ... [2021-04-26T08:53:43.306Z] Caused by: java.security.InvalidParameterException [2021-04-26T08:53:43.308Z] at com.example.moris.Function.run(Function.java:30) [2021-04-26T08:53:43.310Z] ... 16 more [2021-04-26T08:53:43.311Z] . [2021-04-26T08:53:43.313Z] Waiting for `00:00:00` before retrying function execution. Next attempt: '3'. Max retry count: '3' [2021-04-26T08:53:43.314Z] Executing 'Functions.fixeddelayretry' (Reason='This function was programmatically called via the host APIs.', Id=9a499277-61d0-4d2e-b68c-24210b3172a6) [2021-04-26T08:53:43.316Z] Sending invocation id:9a499277-61d0-4d2e-b68c-24210b3172a6 [2021-04-26T08:53:43.317Z] Posting invocation id:9a499277-61d0-4d2e-b68c-24210b3172a6 on workerId:760295b9-ab89-4fbf-84bc-b9e1ac57e8ae [2021-04-26T08:53:43.318Z] HttpContext has ClaimsPrincipal; parsing to gRPC. [2021-04-26T08:53:43.319Z] Writing invocation request invocationId: 9a499277-61d0-4d2e-b68c-24210b3172a6 to workerId: 760295b9-ab89-4fbf-84bc-b9e1ac57e8ae [2021-04-26T08:53:43.326Z] Java HTTP trigger processed a request. [2021-04-26T08:53:43.329Z] Received invocation response for invocationId: 9a499277-61d0-4d2e-b68c-24210b3172a6 from workerId: 760295b9-ab89-4fbf-84bc-b9e1ac57e8ae [2021-04-26T08:53:43.332Z] InvocationResponse received for invocation id: 9a499277-61d0-4d2e-b68c-24210b3172a6 [2021-04-26T08:53:43.354Z] Executed 'Functions.fixeddelayretry' (Failed, Id=9a499277-61d0-4d2e-b68c-24210b3172a6, Duration=21ms) [2021-04-26T08:53:43.357Z] System.Private.CoreLib: Exception while executing function: Functions.fixeddelayretry. System.Private.CoreLib: Result: Failure Exception: InvalidParameterException: Stack: java.lang.reflect.InvocationTargetException [2021-04-26T08:53:43.359Z] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ... [2021-04-26T08:53:43.382Z] Caused by: java.security.InvalidParameterException [2021-04-26T08:53:43.383Z] at com.example.moris.Function.run(Function.java:30) [2021-04-26T08:53:43.384Z] ... 16 more [2021-04-26T08:53:43.385Z] . [2021-04-26T08:53:43.389Z] Executed HTTP request: { [2021-04-26T08:53:43.391Z] requestId: "31cc7ed4-4f9b-4a9e-a5d8-abb0f94d8eb3", [2021-04-26T08:53:43.393Z] identities: "", [2021-04-26T08:53:43.393Z] status: "500", [2021-04-26T08:53:43.394Z] duration: "4284" [2021-04-26T08:53:43.395Z] } まとめ 現在のところ関数側で、リトライの回数とかは取得できなさそうでした。いずれ context インスタンス経由で取得できれば回数把握とかできていいんですけど。とはいえ、アノテーションで指定できるようになったので、積極的に使っていきたいですね。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SpringBootでsmall test JUnit5 × jMockit ver

はじめに SpringBootでテストを、となった際にテストサイズを考えsmall testのUnitテストを実装したいという場面があるが、small testをどのように実装するのかについて記載した記事をあまり見かけないので、自身の備忘録としても残しておく。 ※ググると大抵dependencyにspring test系を持たせているものが見つかるが、ミニマムのunit testを実装する上ではそれらは不要。 この記事を読むと分かる事は以下。 SpringBootにおけるjMockitでのsmall testを導入する際の依存関係(pom.xmlの内容) DI(@Autowired)されているフィールドの扱い方などテストコードの基本的な書き方 small testとは 関連記事の方を参照 カバレッジの話 関連記事の方を参照 JUnit5×jMockitでsmall test SpringBootにおけるjMockitでのsmall testを導入する際の依存関係(pom.xmlの内容) プロジェクト(パッケージ)管理ツールにmavenを使っている場合、pom.xmlは以下のようになる。 ※色々省略しているが、samll testであれば基本的にspring-boot-starter-testなどの依存は不要。 pom.xml <project xmlns="・・・"> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.4.5</version> </parent> <properties> <junit-jupiter.version>5.7.1</junit-jupiter.version> <jmockit.version>1.49</jmockit.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- jdbc, json, lombokなどなど --> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.jmockit</groupId> <artifactId>jmockit</artifactId> <scope>test</scope> </dependency> </dependencies> </project> DI(@Autowired)されているフィールドの扱い方などテストコードの基本的な書き方 まずjMockitを使うため、@ExtendWithでJMockitExtension.classを宣言し継承する。 SpringでDI(@Autowired)されている各フィールドは@Injectableでmock化する。 また、それらをtest対象のクラスにinjectするために、test対象クラスに対しては@Testedを記載しmockを注入する。 基本的なパターンとしては以下のような実装になる。 ※jMockitの使い方・構文についてはここを参照。 ※SpringでのjMockitについてはここを参照。 SampleTest.java // 省略 @ExtendWith(JMockitExtension.class) class SampleTest { @Injectable private Hoge mockHoge; @Injectable private Fuga mockFuga; @Tested private Sample testClass; @Test void testFoo() { mockGetHoge("hoge"); assertEquals("hoge", testClass.foo()); } private void mockGetHoge(String resultValue) { new Expectations() { { mockHoge.getHoge(); result = resultValue; } }; } // 省略 } 関連記事 SpringBootでsmall test JUnit5 × Mockito ver 参考文献 https://testing.googleblog.com/2010/12/test-sizes.html
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SpringBootでsmall test JUnit5×Mockito ver

はじめに SpringBootでテストを、となった際にテスト粒度(サイズ)を考えsmall testのUnitテストを実装したいという場面があるが、small testをどのように実装するのかについて記載した記事をあまり見かけないので、自身の備忘録としても残しておく。 この記事を読むと分かる事は以下。 SpringBootにおけるMockitoでのsmall testを導入する際の依存関係(pom.xmlの内容) DI(@Autowired)されているフィールドの扱い方などテストコードの基本的な書き方 細かいテストの記述方法やテクニック static methodのmock化の方法 テスト対象のメソッドで、自身のクラスのメソッド(private以外)を呼び出すような場合にテストを簡単にするテクニック(spyの利用) 共通のmockの定義方法 引数が可変長のもののmock化方法 small testとは そもそもsmall testとはどういうもの?という話になるが、small testとは以下のようなテストとして定義できる。 ネットワークへアクセスしない データベースへアクセスしない ファイルシステムへアクセスしない 外部システムへアクセスしない シングルスレッドで動作する システムプロパティへのアクセスをしない こうした要件を満たすものをsmall testと今回は呼ぶ事にする。 カバレッジの話 上記のsmall testの概念に加えて、ソースコードのテストにおいて切り離せない話がカバレッジであるが、 いずれのカバレッジでもsmall test実現できるので、ここではどのカバレッジで実装するかは扱わない。 JUnit5×Mockitoでsmall test SpringBootにおけるMockitoでのsmall testを導入する際の依存関係(pom.xmlの内容) プロジェクト(パッケージ)管理ツールにmavenを使っている場合、pom.xmlは以下のようになる。 ※色々省略している。 pom.xml <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>springboot-smalltest</artifactId> <version>0.0.1-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.4.5</version> </parent> <properties> <junit-jupiter.version>5.7.1</junit-jupiter.version> <mockito.version>3.7.7</mockito.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- jdbc, json, lombokなどなど --> <dependency> <groupId>com.googlecode.json-simple</groupId> <artifactId>json-simple</artifactId> <version>1.1.1</version> <exclusions> <exclusion> <groupId>junit</groupId> <artifactId>junit</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-junit-jupiter</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-inline</artifactId> <scope>test</scope> </dependency> </dependencies> </project> ここでは static methodのMock化が行えるようにmockito-inlineを依存に追加 JUnitの依存を持つjson-simpleのJUnit依存をexclusionに指定し除外※自動的に依存が追加されるような場合はexclusionを使い依存を明示的に除外する をしている。 ここで注意として、MockitoであればPowerMockを使いたい場面もあるかもしれないが、現時点でJunit5では使えないよう・・・。 DI(@Autowired)されているフィールドの扱い方などテストコードの基本的な書き方 まずMockitoを使うため、@ExtendWithでMockitoExtension.classを宣言し継承する。 SpringでDI(@Autowired)されている各フィールドは@Mockでmock化する。 また、それらをtest対象のクラスにinjectするために、test対象クラスに対しては@InjectMocksを記載しmockを注入する。 基本的なパターンとしては以下のような実装になる。 ※Mockitoの使い方・構文についてはここを参照。 SampleTest.java // 省略 @ExtendWith(MockitoExtension.class) class SampleTest { @Mock private Hoge mockHoge; @Mock private Fuga mockFuga; @InjectMocks private Sample testClass; @Test void testFoo() { doReturn("hoge").when(mockHoge).getHoge(); assertEquals("hoge", testClass.foo()); } // 省略 } 細かいテストの記述方法やテクニック static methodのmock化の方法 MockedStaticを利用する。 MockStaticSampleTest.java // 省略 @ExtendWith(MockitoExtension.class) class MockStaticSampleTest { @Mock private Hoge mockHoge; @InjectMocks private MockStaticSample testClass; @Test void testGetCompleteDate() { try (MockedStatic<DateUtil> mocked = mockStatic(DateUtil.class)) { mocked.when(() -> DateUtil.getNumbersOnlyFormat(anyString()).thenReturn("19700101"); assertEquals("19700101", testClass.getCompleteDate()); mocked.verify(() -> DateUtil.getNumbersOnlyFormat(anyString()); } } // 省略 } テスト対象のメソッドで、自身のクラスのメソッド(private以外)を呼び出すような場合にテストを簡単にするテクニック(spyの利用) 以下のようにテスト対象のメソッド内で、自身の他のメソッド(private以外)が呼ばれるような場合、spyを使うと一部だけmock化ができテストを書きやすくなる。 具体的には、自身のメソッドを呼び出しその戻り値を使って分岐をするようなメソッドをテストする際に、カバレッジをC2など複雑なものを採用すると、自身のメソッドの返り値まで考えて複雑な実装しなければならず大変になるが、それを簡単にする事ができる。 PrivateContainSample.java // 省略 class PrivateContainSample { SampleContentsDto foo() { SampleDto dto = generate(commonDto); // 何かの処理 } SampleDto generate(SampleCommonDto dot) { // 何かの処理 } // 省略 } PrivateContainSampleTest.java // 省略 @ExtendWith(MockitoExtension.class) class MockStaticSampleTest { @Mock private Hoge mockHoge; @InjectMocks private PrivateContainSample testClass; private PrivateContainSample sptTestClass; @BeforeEach void setUp() { spyTest = spy(testClass); } @Test void testFoo() { doReturn(new SampleDto()).when(sptTestClass).generate(any(SampleCommonDto.class)); assertNotNull(spyTestClass.foo()); } // 省略 } spyで記述した部分(generate)だけがmock化されるので、fooはmock化されず通常通りテストが実施できる。 共通のmockの定義方法・引数が可変長のもののmock化方法 テスト対象のクラス全体で使われているメソッドで、1つの共通の戻り値があればいいような場合は、@BeforeEachでmock化してしまうと便利。 また、可変長引数の場合はMockito.<String[]>any()のように記述する。 SampleTest.java // 省略 @ExtendWith(MockitoExtension.class) class SampleTest { @Mock private Hoge mockHoge; @Mock private CommonMessage mockCommonMessage; @InjectMocks private Sample testClass; @BeforeEach void setUp() { doReturn(new Message()).when(mockCommonMessage).getMessage(Mockito.<String[]>any()); } @Test void testFoo() { // 何かのテスト } // 省略 } 参考文献 https://testing.googleblog.com/2010/12/test-sizes.html
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SpringBootでsmall test JUnit5 × Mockito ver

はじめに SpringBootでテストを、となった際にテストサイズを考えsmall testのUnitテストを実装したいという場面があるが、small testをどのように実装するのかについて記載した記事をあまり見かけないので、自身の備忘録としても残しておく。 ※ググると大抵dependencyにspring test系を持たせているものが見つかるが、ミニマムのunit testを実装する上ではそれらは不要。 この記事を読むと分かる事は以下。 SpringBootにおけるMockitoを用いたsmall testを導入する際の依存関係(pom.xmlの内容) DI(@Autowired)されているフィールドの扱い方などテストコードの基本的な書き方 細かいテストの記述方法やテクニック static methodのmock化の方法 テスト対象のメソッドで、自身のクラスのメソッド(private以外)を呼び出すような場合にテストを簡単にするテクニック(spyの利用) 共通のmockの定義方法 引数が可変長のもののmock化方法 small testとは そもそもsmall testとはどういうもの?という話になるが、small testとは以下のようなテストとして定義できる。 ネットワークへアクセスしない データベースへアクセスしない ファイルシステムへアクセスしない 外部システムへアクセスしない シングルスレッドで動作する システムプロパティへのアクセスをしない カバレッジの話 上記のsmall testの概念に加えて、ソースコードのテストにおいて切り離せない話でカバレッジがあるが、 いずれのカバレッジでもsmall testは実現できるので、ここではどのカバレッジで実装するかは扱わない。 JUnit5×Mockitoでsmall test SpringBootにおけるMockitoでのsmall testを導入する際の依存関係(pom.xmlの内容) プロジェクト(パッケージ)管理ツールにmavenを使っている場合、pom.xmlは以下のようになる。 ※色々省略しているが、samll testであれば基本的にspring-boot-starter-testなどの依存は不要。 pom.xml <project xmlns="・・・"> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.4.5</version> </parent> <properties> <junit-jupiter.version>5.7.1</junit-jupiter.version> <mockito.version>3.7.7</mockito.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- jdbc, json, lombokなどなど --> <dependency> <groupId>com.googlecode.json-simple</groupId> <artifactId>json-simple</artifactId> <version>1.1.1</version> <exclusions> <exclusion> <groupId>junit</groupId> <artifactId>junit</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-junit-jupiter</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-inline</artifactId> <scope>test</scope> </dependency> </dependencies> </project> ここでは static methodのMock化が行えるようにmockito-inlineを依存に追加 JUnitの依存を持つjson-simpleのJUnit依存をexclusionに指定し除外※自動的に依存が追加されるような場合はexclusionを使い依存を明示的に除外する をしている。 ここで注意として、MockitoであればPowerMockを使いたい場面もあるかもしれないが、現時点でJunit5では使えないよう・・・。 DI(@Autowired)されているフィールドの扱い方などテストコードの基本的な書き方 まずMockitoを使うため、@ExtendWithでMockitoExtension.classを宣言し継承する。 SpringでDI(@Autowired)されている各フィールドは@Mockでmock化する。 また、それらをtest対象のクラスにinjectするために、test対象クラスに対しては@InjectMocksを記載しmockを注入する。 基本的なパターンとしては以下のような実装になる。 ※Mockitoの使い方・構文についてはここを参照。 SampleTest.java // 省略 @ExtendWith(MockitoExtension.class) class SampleTest { @Mock private Hoge mockHoge; @Mock private Fuga mockFuga; @InjectMocks private Sample testClass; @Test void testFoo() { doReturn("hoge").when(mockHoge).getHoge(); assertEquals("hoge", testClass.foo()); } // 省略 } 細かいテストの記述方法やテクニック static methodのmock化の方法 MockedStaticを利用する。 MockStaticSampleTest.java // 省略 @ExtendWith(MockitoExtension.class) class MockStaticSampleTest { @Mock private Hoge mockHoge; @InjectMocks private MockStaticSample testClass; @Test void testGetCompleteDate() { try (MockedStatic<DateUtil> mocked = mockStatic(DateUtil.class)) { mocked.when(() -> DateUtil.getNumbersOnlyFormat(anyString()).thenReturn("19700101"); assertEquals("19700101", testClass.getCompleteDate()); mocked.verify(() -> DateUtil.getNumbersOnlyFormat(anyString()); } } // 省略 } テスト対象のメソッドで、自身のクラスのメソッド(private以外)を呼び出すような場合にテストを簡単にするテクニック(spyの利用) 以下のようにテスト対象のメソッド内で、自身の他のメソッド(private以外)が呼ばれるような場合、spyを使うと一部だけmock化ができテストを書きやすくなる。 具体的には、自身のメソッドを呼び出しその戻り値を使って分岐をするようなメソッドをテストする際に、カバレッジをC2など複雑なものを採用すると、自身のメソッドの返り値まで考えて複雑な実装しなければならず大変になるが、それを簡単にする事ができる。 PrivateContainSample.java // 省略 class PrivateContainSample { SampleContentsDto foo() { SampleDto dto = generate(commonDto); // 何かの処理 } SampleDto generate(SampleCommonDto dot) { // 何かの処理 } // 省略 } PrivateContainSampleTest.java // 省略 @ExtendWith(MockitoExtension.class) class MockStaticSampleTest { @Mock private Hoge mockHoge; @InjectMocks private PrivateContainSample testClass; private PrivateContainSample sptTestClass; @BeforeEach void setUp() { spyTest = spy(testClass); } @Test void testFoo() { doReturn(new SampleDto()).when(sptTestClass).generate(any(SampleCommonDto.class)); assertNotNull(spyTestClass.foo()); } // 省略 } spyで記述した部分(generate)だけがmock化されるので、fooはmock化されず通常通りテストが実施できる。 共通のmockの定義方法・引数が可変長のもののmock化方法 テスト対象のクラス全体で使われているメソッドで、1つの共通の戻り値があればいいような場合は、@BeforeEachでmock化してしまうと便利。 また、可変長引数の場合はMockito.<String[]>any()のように記述する。 SampleTest.java // 省略 @ExtendWith(MockitoExtension.class) class SampleTest { @Mock private Hoge mockHoge; @Mock private CommonMessage mockCommonMessage; @InjectMocks private Sample testClass; @BeforeEach void setUp() { doReturn(new Message()).when(mockCommonMessage).getMessage(Mockito.<String[]>any()); } @Test void testFoo() { // 何かのテスト } // 省略 } 参考文献 https://testing.googleblog.com/2010/12/test-sizes.html
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

継承機能を組み込んでみた。

問題 ログイン情報取得のコードを各コントローラーで読み込む必要があるが、コードが冗長化してしまう。 @Controller public class TestController{ @GetMapping("/index") public String index(Model model) { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); if (auth.getPrincipal() instanceof DbUserDetails) { int loginUserId = ((DbUserDetails) auth.getPrincipal()).getDbaccount().getID(); String userNickname = ((DbUserDetails) auth.getPrincipal()).getDbaccount().getNICKNAME(); String mainImage = ((DbUserDetails) auth.getPrincipal()).getDbaccount().getMAIN_IMAGE(); model.addAttribute("loginUserId", loginUserId); model.addAttribute("loginUserNickname", userNickname); model.addAttribute("loginUserMainImage", mainImage); } } } 仮説 ログイン情報取得のコードをモジュール化し、継承することでコード量を減らすことができるのではないか。 実装① ログイン情報取得コードをCommonクラスとしてモジュール化する。 public class Common { protected int loginProcessing(Model model) { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); if (auth.getPrincipal() instanceof DbUserDetails) { int loginUserId = ((DbUserDetails) auth.getPrincipal()).getDbaccount().getID(); String userNickname = ((DbUserDetails) auth.getPrincipal()).getDbaccount().getNICKNAME(); String mainImage = ((DbUserDetails) auth.getPrincipal()).getDbaccount().getMAIN_IMAGE(); model.addAttribute("loginUserId", loginUserId); model.addAttribute("loginUserNickname", userNickname); model.addAttribute("loginUserMainImage", mainImage); return loginUserId; } return 0; } } 実装② @Controller public class TestController extends Common { @GetMapping("/index") public String index(Model model) { /* 認証したログインユーザー情報を取得するメソッドを実行(Common.javaのloginProcessingメソッド) */ loginUserId = loginProcessing(model); } } 結果 モジュール化したCommonクラスを継承することで、各コントローラーに記載するコード量を減らすことができた。他の箇所でも継承を活用すれば、記述量を減らすことが実現できる。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む