JUnit5試したみた #3(ParameterizedTest)

前回に引き続き、JUnit5について書きます。2018年1月15日に5.0.3がリリースされましたね。今回は、ParameterizedTestについてです。
元のソースコードGitHubにおいてあります。
github.com

ParameterizedTest

ParameterizedTestは、指定した引数を用いて、複数回テストを実行することができます。同じテストだけど、引数だけが違うというときに使えます!
引数のタイプを指定できるアノテーションは、以下の6つです。
@ValueSource、@EnumSource、@MethodSource、@CsvSource、@CsvFileSource、@ArgumentSource
詳しくは、後ほど説明します。

@ValueSource

@ValueSourceは、最もシンプルに引数のタイプを指定できます。
@ValueSourceで指定できる型は、String、int、long、doubleの配列です。配列の前から順番にテストします。
以下がStringの配列を指定したときの例です。int、long、doubleのときも同様です。

テスト対象クラス

テストクラス


実行結果

f:id:b1a9id:20180118101714p:plain

@EnumSource

@EnumSourceで指定できる属性は、value、names、modeがあります。配列の前から順番にテストします。

属性 説明
value Enumのタイプを指定する。
names Valueで指定したEnumのタイプの特定の値をStringの配列で指定する(正規表現も使用可能)。これらの値をテストでどういう扱いにするかをmodeで指定する。
mode EnumSource.Modeのどれか(EXCLUDE, INCLUDE, MATCH_ALL, MATCH_ANY)を指定する。

EnumSource.Modeの説明

説明
EXCLUDE namesで指定した値を除外して抽出する。
INCLUDE namesで指定した値のみ抽出する。namesのデフォルトはこれです。
MATCH_ALL namesで正規表現を指定した場合にすべてに当てはまる値のみ抽出する。
MATCH_ANY namesで正規表現を指定した場合にどれかに当てはまる値のみ抽出する。

なお、namesを未指定でMATCH_ALLまたはMATCH_ANYを指定した場合は、すべての値を抽出します。

テスト対象のEnum

テストクラス


@CsvSource

@CsvSourceで指定できる型は、カンマ区切り文字列の配列です。配列の前から順番にテストします。
説明があまりうまくできていない気がするのでサンプルみてみましょうw
ちなみに指定の仕方は、JUnit 5 User Guideに詳しく書いてあるのでこちらを参照していただければと思います。

テストクラス


実行結果

f:id:b1a9id:20180118145219p:plain

@CsvFileSource

@CsvSourceとほぼ同様ですが、こちらは、ファイルから読み込むことができます。

テスト対象CSV

テストクラス


実行結果

f:id:b1a9id:20180118150155p:plain

解説

ちなみに、@CsvFileSourceは、ファイルの文字コード、改行文字、区切り文字を指定できるようです。(区切り文字は、@CsvSourceでも指定できます。)
区切り文字に「・」を指定して実行してみましたが、表示するメッセージはcsv用になっていました。まあ、アノテーションにもCsvとついていますし、区切り文字は指定しない方がいいかもしれませんね。

@MethodSource

@MethodSourceは、自分で実装した指定したい型のSteramまたはIterableまたはIteratorまたは配列を返すメソッドをStringの配列で指定します。

メソッドの引数が1つのときの、テストクラス

methodSourceIntStreamメソッドの実行結果

f:id:b1a9id:20180118152736p:plain

methodSourceStreamメソッドの実行結果

f:id:b1a9id:20180118152802p:plain

メソッドの引数が2つのときの、テストクラス

実行結果

f:id:b1a9id:20180118153322p:plain

解説

メソッドで複数の引数を指定したい場合は、ArgumentインターフェースのCollectionかStreamを返す必要があります。

@ArgumentsSource

上で紹介したものは全てArgumentsProviderインターフェースを実装したクラスを呼んでいます。
f:id:b1a9id:20180118155745p:plain
つまり、再利用可能なArgumentsProviderを実装して、値を自由に指定しようということです。

テストクラス

実行結果

f:id:b1a9id:20180118162125p:plain

おまけ(表示するメッセージをカスタマイズする)

@ParameterizedTestのname属性を指定することで、表示するメッセージをカスタマイズできます。

サンプルコード

実行結果

f:id:b1a9id:20180118163135p:plain

解説

次のプレースホルダが使えます。

プレースホルダ 説明
{index} 現在の呼び出し回数
{arguments} メソッドの引数のリスト(カンマ区切りで表示される)
{0}, {1},... 個々の引数

以上です。次はDynamicTestとか紹介できたらいいかなと思います。

JUnit5試したみた #2 (RepeatedTest)

前回に引き続き、JUnit5について書きます。今回は、RepeatedTestについてです。

RepeatedTest

指定した回数@RepeatedTestを付与したテストメソッドを実行してくれます。具体的に言うと、@RepeatedTestのvalue属性に実行したい回数を指定します。

サンプルコード

実行結果

f:id:b1a9id:20171230113258p:plain

解説

BeforeEachは、各テストの各繰り返し回数ごとに実行される

以下の結果から見出しのことが言えます。

表示名がカスタマイズできる

RepeatedTestでは、先に説明したことに加えて、繰り返し回数ごとに表示名(displayName)をカスタマイズすることができます。デフォルトの表示名は、RepeatedTest.SHORT_DISPLAY_NAMEである、「repetition [現在の実行回数] of [実行すべき回数]」です。
静的なテキストと動的なプレースホルダーから成り立つメッセージを、@RepeatedTestのname属性に指定します。
用意されているプレースホルダーは、次の3つになります。

{displayName} @RepeatedTestメソッドのdisplayName
{currentRepetition} 現在の実行回数
{totalRepetitions} 実行すべき回数

繰り返し回数ごとに表示名を変更しているテストが、customDisplayName()メソッドとcustomDisplayNameLongPattern()メソッドになります。
後者のテストでは、name属性にRepeatedTest.LONG_DISPLAY_NAMEを指定しています。実際に表示されるメッセージは、「Details... :: repetition [現在の実行回数] of [実行すべき回数]」です。

ここで、現在の実行回数や実行すべき回数ってどう取得すればいいの?って疑問がでてくると思います。
RepetitionInfoインターフェースをインジェクトすることで解決できます。
これは、getCurrentRepetition()メソッドとgetTotalRepetitions()メソッドを持ったインターフェースで、@RepeatedTest、@BeforeEach、@AfterEachが付与されたメソッドにのみインジェクトすることができます。
@RepeatedTestが付与されたメソッドが存在しないクラスで、@BeforeEachまたは@AfterEachが付与されたメソッドにRepetitionInfoをインジェクトしようとすると、ParameterResolutionExceptionがスローされます。
customDisplayName()メソッドを見てもらえば、使い方がわかると思います。

次回は、ParameterizedTestについて書きます。

JUnit5試したみた #1

JUnit5が9/10にリリースされました。リリースからちょっと時間が経ってしましましたが、JUnit5について書きたいと思います。

What' new JUnit5?

大きな違いと言えば、3つの異なるサブプロジェクトから構成されていることです。以前のバージョンまでは、1プロジェクトで構成されていました。
つまり、こういうことです。
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit VintageJUnit 5 User Guide より)

各プロジェクトの役割について簡単に説明します。

JUnit Platform

JVM上でテストフレームワークを動かすための基盤を提供します。

JUnit Jupiter

プラットフォーム上でJupiterベースのテストを動かすためのテストエンジンを提供します。

JUnit Vintage

プラットフォーム上でJUnit3やJUnit4ベースのテストを動かすためのテストエンジンを提供します。

JUnit5の導入

pom.xmlにこれらを追加してください。なお、JUnit5を動かすためには、Java8以上である必要があります。

それぞれの説明は JUnit 5 User Guide に書いてあります。

pom.xml

一般的なテスト

サンプルコードです。

StandardTest.java

アノテーションの説明

テストを書く上で、よく使うアノテーションについて説明します。

@DisplayName

テストクラスやメソッドにつけることができるアノテーションです。
テストランナーやテストレポートが引数で指定した値を表示してくれます。文字列、空白、絵文字などを使うことができます。

@BeforeAll

JUnit4でいうところの@BeforeClassです。
全ての@Test, @RepeatedTest, @ParameterizedTest, and @TestFactory テスト実行前に1度だけ実行されます。

@BeforeEach

JUnit4でいうところの@Beforeです。
@Test, @RepeatedTest, @ParameterizedTest, and @TestFactory 各テスト実行前に実行されます。

@Disabled

JUnit4でいうところの@Ignoreです。
これをつけたテストクラス及びメソッドは実行されません。

実行結果

IntelliJ IDEAで実行しています。

@DisplayNameの表示

@DisplayNameで指定した値が表示されていることがわかります。今まではコメントとしてどういったテストか書いていたのでありがたいです。
f:id:b1a9id:20171226114300p:plain

結果出力

@BeforeAllがクラス内で1度だけ、@BeforeEachが各テスト前に実行されていることがわかります。

executed before all test just once.
executed before each test.
This is test1.

This is Disabled test.
executed before each test.
This is test3.

基本的なアサーションの紹介

サンプルコードです。
基本的にはJavaDocを読めば理解できると思います。

StandardAssertionTest.java

Assertions#assertAll

第1引数で指定している値は、テスト失敗時にメッセージに含みたい値になります。
DependentAssertions#nameAssertionsの失敗例です。

org.opentest4j.MultipleFailuresError: This is name test. (1 failure)
	expected: <Ryo> but was: <Ryosuke>

Assertions#fail

AssertionFailedErrorを投げて必ずテストを失敗させる。値を返す前に例外を投げる。AssertJのassertThatThrowByを使ってたら使い道なさそう...


Assumption

Assumption#assumeTrueとかの結果がTrueだったら、それ以降に書いた検証を実行します。厳密には、TestAbortedExceptionを投げてテストを中断させています。
AssumptionAssertions#invalidTestでAssumption#assumeFalseの第2引数に渡した「() -> "This is invalid."」は、テストが中断したときにメッセージに含みたい値になります。

org.opentest4j.TestAbortedException: Assumption failed: This is invalid.

実行結果

AssumptionAssertionsクラスで中断されたテストは結果に出力されていないことがわかると思います。中断されたことは、ログに出力されます。
f:id:b1a9id:20171226154151p:plain

今回は、JUnit5の導入的なところ触れてみました。次回は実用的なテストを書こうと思います。
ちなみに使用したサンプルコードはこちらにあります。
github.com

JJUG CCC 2017 FallでAssertJを推してきた

11/18(土)に開催されたJJUG CCC 2017 Fallで1年ぶりに登壇してきました。
タイトルは、「ユニットテストアサーション 流れるようなインターフェースのAssertJを添えて 入門者仕立て」でした。(フレンチのメニューをオマージュしました)
f:id:b1a9id:20171124004247j:plain

50人くらいの部屋だし、11時からだったので半分入ればいいかなと思っていました。でも10分前くらいには既に70人くらい入っていて緊張MAX!!!

何回か練習したから大丈夫と思ってたけどやはり人前に立つと緊張する〜
AssertJ使ったことある人という質問に対して、1割いるかいないかでしたね。素敵なのに案外使われていないことが判明!
自分のレベルの話しでも、AssertJいいなって思ってくださった人がけっこういて非常によかった♪
今回は、大事故スライドなしで挑んでよかったわw(当日の2時まで入ってたけど消した)
ちなみにスライドはこちら。

www.slideshare.net

話して感じたことここまでにして、アンケートで質問等をちょっといただいたのでそれの回答をしようかと思います!

いただいた意見と回答

1.JUnitとの記述の比較がとてもわかりやすかったです。使っている人がまだ少なそうなので、情報を探すのは大変ではなかったでしょうか。そのような点もお話聞かせていただきたいと思いました。

結論から述べると、公式リファレンス読んで、JavaDoc読んで、コード読みました。
まず、AssertJを使おうと思ったきっかけはこの本です!
gihyo.jp

これを見ていた時ちょうど、会社でユニットテストライン80%目指そうぜ計画がはじまりました。
Serviceのテストを書いていて、assertEqualsいっぱいだし、コピペでつまんないってって思っていました。そこでAssertJを書いてみると、書きやすさ、コードの美しさ、エラーメッセージのわかりやすさどれをとっても素敵でした。
AssertJは、1年くらい使ってるのでそれなりの知見はあったのですが、登壇のためにリファレンスを1から読みました。そして実際に手を動かしました。
AssertJの公式リファレンスは、ロードオブザリングとかスターウォーズのキャラクター出てきて案外楽しく読めます!JavaDocは、書き方例が書いてあって理解しやすいです。

2.自分も知らない使い方の紹介もあって良かったです hamcrest matcherとの違いやメソッドチェーンの説明があるともっとわかりやすかったと思いました。

既に使っていらっしゃるんですね。ためになること話せてよかったです。
なお、hamcrest matcherの経験はほぼ皆無(ほぼAssertJしか使ったことない)なのです。
メソッドチェーンの説明とは、それぞれの検証のことですかね?次回に活かします!


以上です。
登壇するとフィードバックがよくても悪くてもとても嬉しいです。
またネタ探して登壇したいと思います!!!

AssertJで流れるようなインターフェースでアサーションを書こう #1

みなさんユニットテスト書いてますか?アサーションライブラリは何を使ってますか?私は、Assert Jが好きで使ってます。

AssertJは、Javaアサーションライブラリで、主にAssertJ CoreとAssertJ SwingとAssertJ DBがあります。
今回は、AssertJ Coreについて書きます。第1回は、概要と導入手順です。
AssertJ Coreは、流れるようなインターフェースでアサーションを書くことができるJavaライブラリで、テストコードを読みやすく改善し、簡単にメンテナンスしやすくすることが主な目的です。
メソッドチェーンで複数の検証を一度に書くことができます。また、JDKの標準タイプのアサーションを提供していて、JUnitTestNGで使うことができます。

バージョン

バージョンは、下の表のようになっています。(2017/10/19時点)3系の大きな特徴は、Project Lambdaを使用した検証ができることです。また、OptionalやDate and Time APIの各クラスの検証もできます。
これらの機能に加えて、2系のすべての機能が利用できます。

Javaバージョン 初期リリース 最新
3系 Java8以上 2015/04/06(3.0.0) 2017/05/21(3.8.0)
2系 Java7以上 2015/03/07(2.0.0) 2017/05/21(2.8.0)
1系 Java6以上 2013/03/26(1.0.0) 2015/01/02(1.7.1)

類似ライブラリ

AssertJ Android

AssertJを拡張したAndroid用のアサーションライブラリです。
square.github.io

Assert Py

AssertJに影響を受けて作られてたPython用のアサーションライブラリです。
github.com

導入手順

依存関係の追加

Spring Boot(>=1.4)を使う場合

その他の場合

  • Gradle

Spring Boot(>=1.4)を使う場合

その他の場合

Spring Bootを使う場合、spring-boot-starter-testには、AssertJが含まれています。しかし、デフォルトで2.6.0が入っているので3.8.0を使う場合は、バージョン指定が必要です。

依存ライブラリがないので、導入がしやすいです。
次回は、実際にどういう風にアサーションが書けるかについてです。

ThymeleafでNumber.javaを継承したクラスをメッセージ式のパラメータとして渡すと千の位で区切ってくれる話

先日、仕事でThymeleafで千の位で区切って表示することをやりました。いわゆる金額表示です。
結論から述べると、以下のようにすると、実現できます。

ちなみにこれはたまたま発見できちゃいました。。。笑
最初は、次のように書いてました。

<p th:text="${#numbers.formatInteger(1234567890, 3, 'COMMA')} + '円'">1,234,567,890円</p>

ある記事を読んでいてメッセージ式にパラメータを渡せることを知りました。
そこでカンマ区切りのことは気にせず、上のように書いてみたら、金額表示になってました。
しかし、なぜ数値をパラメータとして渡すと千の位で区切ってくれる(区切って欲しくないときもあるけど)のか気になったのでソースコード追ってみました。
MessageSourceインターフェースのgetMessageメソッドにブレークポイントを張りました。
MessageFormat.java#subformatが気になりました。

案の定、16行目に止まり、ここでsubformatにDecimalFormat.javaを返していました。
そして、DecimalFormat.javaのsubformatメソッドに処理がうつり、ここで実際に1000の位に区切っていました。それが以下の箇所です。

実際にソースを読んでもらえればわかるのですが、groupingという変数に「,」が格納されています。
DecimalFormatSymbols (Java Platform SE 8 )
ループで回し、パラメタを1個ずつ渡して、1000の位で「,」を入れています。

ちなみに、NumberFormatProvider.javaにこのようなメソッドがあったのでこれをうまく呼ぶ方法を見つけたいなと思いました。

public abstract NumberFormat getCurrencyInstance(Locale locale);

以上です。

Intelli Jのショートカット一覧が日本語訳されました

以前、Intelli Jのショートカットを以下の記事でまとめたのですが、公式で発表されてるのもを日本語訳された会社さんがいらっしゃいました。
uchi-fashion.hatenablog.com

こっちの方が私の拙い日本語訳より参考になると思うのでぜひ!
IntelliJ や PhpStorm などの日本語化