自動化されたソフトウェアテストは、ソフトウェアプロジェクトの長期的な品質、保守性、および拡張性にとって非常に重要です。Javaの場合、JUnitは自動化への道です。
この記事のほとんどは、堅牢な単体テストの作成とスタブ、モック、依存性注入の利用に焦点を当てていますが、JUnitと統合テストについても説明します。
ザ・ JUnitテストフレームワーク は、Javaベースのプロジェクトをテストするための一般的な無料のオープンソースツールです。
この記事の執筆時点では、 JUnit 4 は現在のメジャーリリースであり、10年以上前にリリースされており、最後の更新は2年以上前です。
JUnit 5 (Jupiterプログラミングおよび拡張モデルを使用)は活発に開発されています。で導入された言語機能をより適切にサポートします Java 8 その他の新しい興味深い機能が含まれています。 JUnit 5をすぐに使用できるチームもあれば、5が正式にリリースされるまでJUnit4を使い続けるチームもあります。両方の例を見ていきます。
JUnitテストはIntelliJで直接実行できますが、Eclipse、NetBeans、さらにはコマンドラインなどの他のIDEでも実行できます。
テスト、特に単体テストは、常にビルド時に実行する必要があります。問題が本番環境にあるかテストコードにあるかに関係なく、テストが失敗したビルドは失敗したと見なす必要があります。これには、チームからの規律と、失敗したテストの解決を最優先する意欲が必要ですが、以下を遵守する必要があります。自動化の精神。
JUnitテストは、Jenkinsのような継続的インテグレーションシステムによって実行およびレポートすることもできます。 Gradle、Maven、Antなどのツールを使用するプロジェクトには、ビルドプロセスの一部としてテストを実行できるという追加の利点があります。
JUnit 5のサンプルGradleプロジェクトとして、 JUnitユーザーガイドのGradleセクション そしてその junit5-samples.git リポジトリ。 JUnit 4 API(と呼ばれる)を使用するテストも実行できることに注意してください。 'ビンテージ' )。
プロジェクトは、メニューオプションの[ファイル]> [開く...]> [junit-gradle-consumer sub-directory
]に移動してIntelliJで作成できます。 > OK>プロジェクトとして開く> [OK]をクリックして、Gradleからプロジェクトをインポートします。
Eclipseの場合、 BuildshipGradleプラグイン ヘルプ> Eclipseマーケットプレイスからインストールできます…プロジェクトはファイル>インポート…> Gradle> Gradleプロジェクト>次へ>次へ> junit-gradle-consumer
を参照してインポートできます。サブディレクトリ>次へ>次へ>終了。
IntelliJまたはEclipseでGradleプロジェクトをセットアップした後、Gradleを実行しますbuild
タスクには、test
を使用してすべてのJUnitテストを実行することが含まれます。仕事。 build
の後続の実行では、テストがスキップされる可能性があることに注意してください。コードに変更が加えられていない場合。
JUnit 4については、JUnitの Gradlewikiで使用する 。
JUnit 5については、 ユーザーガイドのMavenセクション そしてその junit5-samples.git Mavenプロジェクトの例のリポジトリ。これは、ビンテージテスト(JUnit 4 APIを使用するテスト)も実行できます。
IntelliJで、[ファイル]> [開く...]を使用してjunit-maven-consumer/pom.xml
に移動します> OK>プロジェクトとして開きます。その後、テストはMavenプロジェクト> junit5-maven-consumer>ライフサイクル>テストから実行できます。
Eclipseで、[ファイル]> [インポート...]> [Maven]> [既存のMavenプロジェクト]> [次へ]> [junit-maven-consumer
]を参照します。ディレクトリ> pom.xml
を使用選択>終了。
Mavenビルドとしてプロジェクトを実行することでテストを実行できます…> test
の目標を指定します>実行します。
JUnit 4については、を参照してください。 MavenリポジトリのJUnit 。
GradleやMavenなどのビルドツールを介してテストを実行することに加えて、多くのIDEはJUnitテストを直接実行できます。
IntelliJ IDEA JUnit 5テストには2016.2以降が必要ですが、JUnit4テストは古いバージョンのIntelliJで機能するはずです。
この記事の目的のために、私のGitHubリポジトリの1つからIntelliJで新しいプロジェクトを作成することをお勧めします( JUnit5IntelliJ.git または JUnit4IntelliJ.git )、単純なPerson
のすべてのファイルが含まれますクラスの例と組み込みのJUnitライブラリを使用します。テストは、[実行]> [すべてのテストの実行]で実行できます。テストは、IntelliJでPersonTest
から実行することもできます。クラス。
これらのリポジトリは、新しいIntelliJ Javaプロジェクトで作成され、ディレクトリ構造を構築しますsrc/main/java/com/example
およびsrc/test/java/com/example
。 src/main/java
src/test/java
中に、ディレクトリがソースフォルダとして指定されましたテストソースフォルダとして指定されました。 PersonTest
を作成した後@Test
で注釈が付けられたテストメソッドを持つクラスでは、コンパイルに失敗する可能性があります。その場合、IntelliJは、IntelliJIDEAディストリビューションからロードできるクラスパスにJUnit4またはJUnit5を追加することを提案します(を参照)。 これらの答え 詳細については、Stack Overflowをご覧ください)。最後に、すべてのテストにJUnit実行構成が追加されました。
も参照してください IntelliJテストハウツーガイドライン 。
空っぽ Java Eclipseのプロジェクトにはテストルートディレクトリがありません。これは、プロジェクトのプロパティ> Javaビルドパス>フォルダの追加…>新しいフォルダの作成…>フォルダ名の指定>終了から追加されました。新しいディレクトリがソースフォルダとして選択されます。残りの両方のダイアログで[OK]をクリックします。
JUnit 4テストは、[ファイル]> [新規]> [JUnitテストケース]で作成できます。 「NewJUnit4 test」と、新しく作成したテスト用のソースフォルダーを選択します。 「テスト対象クラス」と「パッケージ」を指定し、パッケージがテスト対象クラスと一致することを確認します。次に、テストクラスの名前を指定します。ウィザードの終了後、プロンプトが表示されたら、ビルドパスに「JUnit4ライブラリを追加」を選択します。プロジェクトまたは個々のテストクラスは、JUnitテストとして実行できます。も参照してください Eclipseの作成とJUnitテストの実行 。
NetBeansは、JUnit4テストのみをサポートします。テストクラスは、NetBeansJavaプロジェクトで[ファイル]> [新しいファイル...]> [ユニットテスト]> [JUnitテスト]または[既存のクラスのテスト]で作成できます。デフォルトでは、テストルートディレクトリの名前はtest
です。プロジェクトディレクトリ内。
非常に単純なPerson
の製品コードとそれに対応する単体テストコードの簡単な例を見てみましょう。クラス。サンプルコードは私のからダウンロードできます githubプロジェクト IntelliJ経由で開きます。
package com.example; class Person { private final String givenName; private final String surname; Person(String givenName, String surname) { this.givenName = givenName; this.surname = surname; } String getDisplayName() { return surname + ', ' + givenName; } }
ザ・ 不変 Person
クラスにはコンストラクターとgetDisplayName()
があります方法。それをテストしたいgetDisplayName()
期待どおりにフォーマットされた名前を返します。単一ユニットテスト(JUnit 5)のテストコードは次のとおりです。
package com.example; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; class PersonTest { @Test void testGetDisplayName() { Person person = new Person('Josh', 'Hayden'); String displayName = person.getDisplayName(); assertEquals('Hayden, Josh', displayName); } }
PersonTest
JUnit5を使用@Test
とアサーション。 JUnit 4の場合、PersonTest
クラスとメソッドはパブリックである必要があり、異なるインポートを使用する必要があります。これが JUnit4の例の要点 。
PersonTest
を実行するとIntelliJのクラスでは、テストに合格し、UIインジケーターが緑色になります。
必須ではありませんが、テストクラスの命名には一般的な規則を使用します。具体的には、テストするクラスの名前(Person
)から始めて、それに「Test」を追加します(PersonTest
)。テストメソッドの命名も同様で、テスト対象のメソッド(getDisplayName()
)から始まり、それに「test」を付加します(testGetDisplayName()
)。テストメソッドの命名には他にも完全に受け入れられる規則がたくさんありますが、チームとプロジェクト全体で一貫していることが重要です。
本番環境での名前 | テストでの名前 |
---|---|
人 | 人のテスト |
getDisplayName() | testDisplayName() |
また、テストコードを作成する規則を採用していますPersonTest
プロダクションコードのcom.example
と同じパッケージ(Person
)のクラスクラス。テストに別のパッケージを使用した場合は、パブリックを使用する必要があります アクセス編集 単体テストによって参照される本番コードクラス、コンストラクター、およびメソッドでは、適切でない場合でも、それらを同じパッケージに保持することをお勧めします。ただし、リリースされた本番ビルドには通常テストコードを含めたくないため、個別のソースディレクトリ(src/main/java
とsrc/test/java
)を使用します。
@Test
アノテーション(JUnit 4 / 5 )testGetDisplayName()
を実行するようにJUnitに指示しますテストメソッドとしてメソッドを実行し、合格か不合格かを報告します。すべてのアサーション(存在する場合)が合格し、例外がスローされない限り、テストは合格と見なされます。
テストコードは、次の構造パターンに従います。 Arrange-Act-Assert(AAA) 。他の一般的なパターンには、Given-When-ThenおよびSetup-Exercise-Verify-Teardown(通常、単体テストではティアダウンは明示的に必要ありません)が含まれますが、この記事ではAAAを使用します。
テスト例がAAAにどのように準拠しているかを見てみましょう。最初の行である「配置」はPerson
を作成しますテストされるオブジェクト:
Person person = new Person('Josh', 'Hayden');
2行目「見せかけの日々」 演習 プロダクションコードのPerson.getDisplayName()
方法:
String displayName = person.getDisplayName();
3行目の「assert」は、結果が期待どおりであることを確認します。
assertEquals('Hayden, Josh', displayName);
内部的には、assertEquals()
呼び出しは、「Hayden、Josh」文字列オブジェクトのequalsメソッドを使用して、製品コード(displayName
)から返された実際の値が一致することを確認します。一致しなかった場合、テストは失敗とマークされます。
多くの場合、テストにはこれらのAAAフェーズごとに複数の行があることに注意してください。
いくつかのテスト規則について説明したので、本番コードをテスト可能にすることに注意を向けましょう。
Person
に戻りますクラスでは、生年月日に基づいて年齢を返すメソッドを実装しました。コード例では、Java8が新しい日付と機能的なAPIを利用する必要があります。これが新しいPerson.java
です。クラスは次のようになります。
// ... class Person { // ... private final LocalDate dateOfBirth; Person(String givenName, String surname, LocalDate dateOfBirth) { // ... this.dateOfBirth = dateOfBirth; } // ... long getAge() { return ChronoUnit.YEARS.between(dateOfBirth, LocalDate.now()); } public static void main(String... args) { Person person = new Person('Joey', 'Doe', LocalDate.parse('2013-01-12')); System.out.println(person.getDisplayName() + ': ' + person.getAge() + ' years'); // Doe, Joey: 4 years } }
このクラスを実行すると(執筆時点)、Joeyが4歳であることがわかります。テストメソッドを追加しましょう:
// ... class PersonTest { // ... @Test void testGetAge() { Person person = new Person('Joey', 'Doe', LocalDate.parse('2013-01-12')); long age = person.getAge(); assertEquals(4, age); } }
今日は過ぎますが、1年後に実行するとどうなるでしょうか。期待される結果はテストを実行しているシステムの現在の日付に依存するため、このテストは非決定論的で脆弱です。
本番環境で実行する場合、現在の日付LocalDate.now()
を使用して人の年齢を計算しますが、1年後でも確定的なテストを行うには、テストで独自のcurrentDate
を指定する必要があります。値。
これは依存性注入として知られています。 Person
は必要ありません現在の日付自体を決定するオブジェクトですが、代わりにこのロジックを依存関係として渡します。単体テストでは既知のスタブ値が使用され、本番コードでは実行時にシステムから実際の値を提供できます。
LocalDate
を追加しましょうPerson.java
へのサプライヤー:
ノードjsが使用される理由
// ... class Person { // ... private final LocalDate dateOfBirth; private final Supplier currentDateSupplier; Person(String givenName, String surname, LocalDate dateOfBirth) { this(givenName, surname, dateOfBirth, LocalDate::now); } // Visible for testing Person(String givenName, String surname, LocalDate dateOfBirth, Supplier currentDateSupplier) { // ... this.dateOfBirth = dateOfBirth; this.currentDateSupplier = currentDateSupplier; } // ... long getAge() { return ChronoUnit.YEARS.between(dateOfBirth, currentDateSupplier.get()); } public static void main(String... args) { Person person = new Person('Joey', 'Doe', LocalDate.parse('2013-01-12')); System.out.println(person.getDisplayName() + ': ' + person.getAge() + ' years'); // Doe, Joey: 4 years } }
getAge()
のテストを簡単にするためメソッド、currentDateSupplier
、LocalDate
を使用するように変更しました現在の日付を取得するためのサプライヤ。サプライヤーが何かわからない場合は、 Lambdaの組み込み関数型インターフェース 。
また、依存性注入を追加しました。新しいテストコンストラクターを使用すると、テストで独自の現在の日付値を提供できます。元のコンストラクターはこの新しいコンストラクターを呼び出し、LocalDate::now
の静的メソッド参照を渡します。これはLocalDate
を提供します。オブジェクトなので、mainメソッドは以前と同じように機能します。私たちのテスト方法はどうですか?更新しましょうPersonTest.java
:
// ... class PersonTest { // ... @Test void testGetAge() { LocalDate dateOfBirth = LocalDate.parse('2013-01-02'); LocalDate currentDate = LocalDate.parse('2017-01-17'); Person person = new Person('Joey', 'Doe', dateOfBirth, ()->currentDate); long age = person.getAge(); assertEquals(4, age); } }
テストは独自のcurrentDate
を注入するようになりました値なので、来年または任意の年に実行しても、テストは合格します。これは一般的に スタブ 、または返される既知の値を提供しますが、最初に変更する必要がありましたPerson
この依存関係を注入できるようにします。
注意してください ラムダ構文 (()->currentDate
)Person
を構築するときオブジェクト。これは、新しいコンストラクターの要求に応じて、LocalDate
のサプライヤーとして扱われます。
Person
の準備ができました外の世界と通信するためのオブジェクト(その存在全体がJVMメモリにある)。 2つのメソッドを追加します:publishAge()
その人の現在の年齢とgetThoseInCommon()
を投稿するメソッドPerson
と同じ誕生日または同じ年齢の有名人の名前を返すメソッド。 「PeopleBirthdays」と呼ばれる、対話できるRESTfulサービスがあるとします。単一のクラスBirthdaysClient
で構成されるJavaクライアントがあります。
package com.example.birthdays; import java.io.IOException; import java.util.Arrays; import java.util.Collection; public class BirthdaysClient { public void publishRegularPersonAge(String name, long age) throws IOException { System.out.println('publishing ' + name + ''s age: ' + age); // HTTP POST with name and age and possibly throw an exception } public Collection findFamousNamesOfAge(long age) throws IOException { System.out.println('finding famous names of age ' + age); return Arrays.asList(/* HTTP GET with age and possibly throw an exception */); } public Collection findFamousNamesBornOn(int month, int dayOfMonth) throws IOException { System.out.println('finding famous names born on day ' + dayOfMonth + ' of month ' + month); return Arrays.asList(/* HTTP GET with month and day and possibly throw an exception */); } }
Person
を強化しましょうクラス。 publishAge()
の望ましい動作のための新しいテストメソッドを追加することから始めます。機能ではなく、なぜテストから始めるのですか?テスト駆動開発(TDDとも呼ばれます)の原則に従っています。最初にテストを記述し、次にテストに合格するためのコードを記述します。
// … class PersonTest { // … @Test void testPublishAge() { LocalDate dateOfBirth = LocalDate.parse('2000-01-02'); LocalDate currentDate = LocalDate.parse('2017-01-01'); Person person = new Person('Joe', 'Sixteen', dateOfBirth, ()->currentDate); person.publishAge(); } }
この時点で、publishAge()
を作成していないため、テストコードのコンパイルに失敗します。呼び出しているメソッド。空のPerson.publishAge()
を作成したらメソッド、すべてが通過します。これで、その人の年齢が実際にBirthdaysClient
に公開されることを確認するためのテストの準備が整いました。
これは単体テストであるため、高速かつメモリ内で実行する必要があります。そのため、テストはPerson
を構築します。モックのあるオブジェクトBirthdaysClient
したがって、実際にはWebリクエストを行いません。次に、テストはこのモックオブジェクトを使用して、期待どおりに呼び出されたことを確認します。これを行うには、依存関係を追加します Mockitoフレームワーク (MITライセンス)モックオブジェクトを作成してから、モックを作成するBirthdaysClient
オブジェクト:
// ... import com.example.birthdays.BirthdaysClient; // ... import static org.mockito.Mockito.mock; class PersonTest { private BirthdaysClient birthdaysClient = mock(BirthdaysClient.class); // ... @Test void testPublishAge() { // ... Person person = new Person('Joe', 'Sixteen', dateOfBirth, ()->currentDate, birthdaysClient); // ... } }
さらに、Person
の署名を拡張しましたBirthdaysClient
を取るコンストラクターオブジェクト、およびモックを注入するようにテストを変更しましたBirthdaysClient
オブジェクト。
次に、testPublishAge
の最後に追加しますBirthdaysClient
への期待と呼ばれます。 Person.publishAge()
新しいPersonTest.java
に示すように、これを呼び出す必要があります。
// ... class PersonTest { // ... @Test void testPublishAge() throws IOException { // ... Person person = new Person('Joe', 'Sixteen', dateOfBirth, ()->currentDate, birthdaysClient); verifyZeroInteractions(birthdaysClient); person.publishAge(); verify(birthdaysClient).publishRegularPersonAge('Joe Sixteen', 16); } }
Mockitoで強化されたBirthdaysClient
メソッドに対して行われたすべての呼び出しを追跡します。これにより、BirthdaysClient
に対して行われた呼び出しがないことを確認できます。 verifyZeroInteractions()
でpublishAge()
を呼び出す前のメソッド。ほぼ間違いなく必要ではありませんが、これを行うことで、コンストラクターが不正な呼び出しを行わないようにします。 verify()
について行では、BirthdaysClient
への呼び出しをどのように期待するかを指定します見てください。
publishRegularPersonAgeの署名にはIOExceptionがあるため、テストメソッドの署名にも追加することに注意してください。
この時点で、テストは失敗します。
Wanted but not invoked: birthdaysClient.publishRegularPersonAge( 'Joe Sixteen', 16L ); -> at com.example.PersonTest.testPublishAge(PersonTest.java:40)
テスト駆動開発を行っているため、Person.java
に必要な変更をまだ実装していないことを考えると、これは予想されることです。必要な変更を加えて、このテストに合格します。
// ... class Person { // ... private final BirthdaysClient birthdaysClient; Person(String givenName, String surname, LocalDate dateOfBirth) { this(givenName, surname, dateOfBirth, LocalDate::now, new BirthdaysClient()); } // Visible for testing Person(String givenName, String surname, LocalDate dateOfBirth, Supplier currentDateSupplier, BirthdaysClient birthdaysClient) { // ... this.birthdaysClient = birthdaysClient; } // ... void publishAge() { String nameToPublish = givenName + ' ' + surname; long age = getAge(); try { birthdaysClient.publishRegularPersonAge(nameToPublish, age); } catch (IOException e) { // TODO handle this! e.printStackTrace(); } } }
プロダクションコードコンストラクターに新しいBirthdaysClient
とpublishAge()
をインスタンス化させましたbirthdaysClient
を呼び出します。すべてのテストに合格します。すべてが緑です。すごい!ただし、publishAge()
に注意してくださいIOExceptionを飲み込んでいます。バブルを発生させる代わりに、PersonException.java
という新しいファイルで独自のPersonExceptionを使用してラップします。
package com.example; public class PersonException extends Exception { public PersonException(String message, Throwable cause) { super(message, cause); } }
このシナリオは、PersonTest.java
の新しいテストメソッドとして実装します。
// ... class PersonTest { // ... @Test void testPublishAge_IOException() throws IOException { LocalDate dateOfBirth = LocalDate.parse('2000-01-02'); LocalDate currentDate = LocalDate.parse('2017-01-01'); Person person = new Person('Joe', 'Sixteen', dateOfBirth, ()->currentDate, birthdaysClient); IOException ioException = new IOException(); doThrow(ioException).when(birthdaysClient).publishRegularPersonAge('Joe Sixteen', 16); try { person.publishAge(); fail('expected exception not thrown'); } catch (PersonException e) { assertSame(ioException, e.getCause()); assertEquals('Failed to publish Joe Sixteen age 16', e.getMessage()); } } }
モッキートdoThrow()
スタブを呼び出すbirthdaysClient
publishRegularPersonAge()
のときに例外をスローしますメソッドが呼び出されます。 PersonException
の場合スローされない場合、テストに失敗します。それ以外の場合は、例外が 適切に連鎖 IOExceptionを使用して、例外メッセージが期待どおりであることを確認します。現在、本番コードに処理を実装していないため、予期された例外がスローされなかったため、テストは失敗します。 Person.java
で変更する必要があるものは次のとおりですテストに合格するには:
// ... class Person { // ... void publishAge() throws PersonException { // ... try { // ... } catch (IOException e) { throw new PersonException('Failed to publish ' + nameToPublish + ' age ' + age, e); } } }
Person.getThoseInCommon()
を実装しますメソッド、Person.Java
を作成しますクラスは次のようになります この 。
testGetThoseInCommon()
は、testPublishAge()
とは異なり、birthdaysClient
に対して特定の呼び出しが行われたことを確認しません。メソッド。代わりにwhen
を使用しますスタブへの呼び出しは、findFamousNamesOfAge()
への呼び出しの戻り値を返しますおよびfindFamousNamesBornOn()
そのgetThoseInCommon()
作る必要があります。次に、指定した3つのスタブ名がすべて返されることを表明します。
複数のアサーションをassertAll()
でラップするJUnit 5メソッドでは、最初に失敗したアサーションの後で停止するのではなく、すべてのアサーションを全体としてチェックできます。 assertTrue()
のメッセージも含まれています含まれていない特定の名前を識別するため。 「ハッピーパス」(理想的なシナリオ)のテスト方法は次のようになります(これは「ハッピーパス」であるという性質上、堅牢な一連のテストではありませんが、その理由については後で説明します。
// ... class PersonTest { // ... @Test void testGetThoseInCommon() throws IOException, PersonException { LocalDate dateOfBirth = LocalDate.parse('2000-01-02'); LocalDate currentDate = LocalDate.parse('2017-01-01'); Person person = new Person('Joe', 'Sixteen', dateOfBirth, ()->currentDate, birthdaysClient); when(birthdaysClient.findFamousNamesOfAge(16)).thenReturn(Arrays.asList('JoeFamous Sixteen', 'Another Person')); when(birthdaysClient.findFamousNamesBornOn(1, 2)).thenReturn(Arrays.asList('Jan TwoKnown')); Set thoseInCommon = person.getThoseInCommon(); assertAll( setContains(thoseInCommon, 'Another Person'), setContains(thoseInCommon, 'Jan TwoKnown'), setContains(thoseInCommon, 'JoeFamous Sixteen'), ()-> assertEquals(3, thoseInCommon.size()) ); } private Executable setContains(Set set, T expected) { return () -> assertTrue(set.contains(expected), 'Should contain ' + expected); } // ... }
見過ごされがちですが、テストコードに重複がないようにすることも同様に重要です。クリーンなコードと次のような原則 「繰り返さないで」 高品質のコードベース、本番コード、テストコードを同様に維持するために非常に重要です。いくつかのテストメソッドがあるため、最新のPersonTest.javaに重複があることに注意してください。
これを修正するために、いくつかのことができます。
IOExceptionオブジェクトをプライベートfinalフィールドに抽出します。
Person
を抽出しますほとんどのPersonオブジェクトは同じパラメーターで作成されているため、オブジェクトを独自のメソッド(この場合はcreateJoeSixteenJan2()
)に作成します。
assertCauseAndMessage()
を作成しますスローされたPersonExceptions
を検証するさまざまなテスト用。
クリーンなコードの結果は、この表現で見ることができます。 PersonTest.java ファイル。
Person
の場合はどうすればよいですかオブジェクトの生年月日が現在の日付よりも遅いですか?アプリケーションの欠陥は、多くの場合、予期しない入力や、コーナー、エッジ、または境界のケースへの先見性の欠如が原因です。これらの状況をできる限り予測することが重要であり、ユニットテストが適切な場所であることがよくあります。 Person
の構築においておよびPersonTest
、予想される例外のテストをいくつか含めましたが、完全ではありませんでした。たとえば、LocalDate
を使用しますタイムゾーンデータを表したり保存したりするものではありません。ただし、LocalDate.now()
を呼び出すと、LocalDate
が返されます。システムのデフォルトのタイムゾーンに基づきます。これは、システムのユーザーのタイムゾーンよりも1日早いまたは遅い可能性があります。これらの要因は、適切なテストと動作を実装して検討する必要があります。
境界もテストする必要があります。 Person
を考えてみましょうgetDaysUntilBirthday()
のオブジェクト方法。テストには、その人の誕生日が今年にすでに過ぎているかどうか、その人の誕生日が今日であるかどうか、うるう年が日数にどのように影響するかを含める必要があります。これらのシナリオは、その人の誕生日の前日、その日の日、および翌年がうるう年であるその人の誕生日の翌日をチェックすることでカバーできます。関連するテストコードは次のとおりです。
// ... class PersonTest { private final Supplier currentDateSupplier = ()-> LocalDate.parse('2015-05-02'); private final LocalDate ageJustOver5 = LocalDate.parse('2010-05-01'); private final LocalDate ageExactly5 = LocalDate.parse('2010-05-02'); private final LocalDate ageAlmost5 = LocalDate.parse('2010-05-03'); // ... @Test void testGetDaysUntilBirthday() { assertAll( createPersonAndAssertValue(ageAlmost5, 1, Person::getDaysUntilBirthday), createPersonAndAssertValue(ageExactly5, 0, Person::getDaysUntilBirthday), createPersonAndAssertValue(ageJustOver5, 365, Person::getDaysUntilBirthday) ); } private Executable createPersonAndAssertValue(LocalDate dateOfBirth, long expectedValue, Function personLongFunction) { Person person = new Person('Given', 'Sur', dateOfBirth, currentDateSupplier); long actualValue = personLongFunction.apply(person); return () -> assertEquals(expectedValue, actualValue); } }
私たちは主に単体テストに焦点を当ててきましたが、JUnitは統合、受け入れ、機能、およびシステムテストにも使用できます。このようなテストでは、サーバーの起動、既知のデータを含むデータベースの読み込みなど、より多くのセットアップコードが必要になることがよくあります。数千の単体テストを数秒で実行できることがよくありますが、大規模な統合テストスイートの実行には数分または数時間かかる場合があります。統合テストは、通常、コード内のすべての順列またはパスをカバーしようとするために使用されるべきではありません。そのためには、単体テストの方が適しています。
フォームへの入力、ボタンのクリック、コンテンツの読み込みの待機などでWebブラウザーを駆動するWebアプリケーションのテストの作成は、通常、次を使用して行われます。 Selenium WebDriver (Apache 2.0ライセンス)「ページオブジェクトパターン」と組み合わせて( SeleniumHQ github wiki そして ページオブジェクトに関するMartinFowlerの記事 )。
JUnitは、ApacheHTTPクライアントやSpringRestテンプレートなどのHTTPクライアントを使用してRESTfulAPIをテストするのに効果的です( HowToDoInJava.comは良い例を提供します )。
Person
の場合オブジェクトの場合、統合テストには実際のBirthdaysClient
の使用が含まれる可能性があります。 People BirthdaysサービスのベースURLを指定する構成で、モックではなく。次に、統合テストはそのようなサービスのテストインスタンスを使用し、誕生日がそれに公開されていることを確認し、返されるサービスで有名人を作成します。
JUnitには、例ではまだ検討していない多くの追加機能があります。いくつかを説明し、他のリファレンスを提供します。
JUnitは、各@Test
を実行するためのテストクラスの新しいインスタンスを作成することに注意してください。方法。 JUnitは、@Test
のすべてまたはそれぞれの前または後に特定のメソッドを実行するためのアノテーションフックも提供します。メソッド。これらのフックは、データベースまたはモックオブジェクトのセットアップまたはクリーンアップによく使用され、JUnit4と5では異なります。
JUnit 4 | JUnit 5 | 静的メソッドの場合? |
---|---|---|
@BeforeClass | @BeforeAll | はい |
@AfterClass | @AfterAll | はい |
@Before | @BeforeEach | 番号 |
@After | @AfterEach | 番号 |
私たちのPersonTest
たとえば、BirthdaysClient
を構成することを選択しました@Test
のモックオブジェクトメソッド自体ですが、複数のオブジェクトを含むより複雑なモック構造を構築する必要がある場合があります。 @BeforeEach
(JUnit 5内)および@Before
(JUnit 4では)これに適していることがよくあります。
@After*
JVMガベージコレクションは単体テスト用に作成されたほとんどのオブジェクトを処理するため、アノテーションは単体テストよりも統合テストで一般的です。 @BeforeClass
および@BeforeAll
アノテーションは、テスト方法ごとではなく、コストのかかるセットアップおよびティアダウンアクションを1回実行する必要がある統合テストに最も一般的に使用されます。
JUnit 4については、 テストフィクスチャガイド (一般的な概念はJUnit 5にも適用されます)。
すべてのテストではなく、複数の関連するテストを実行したい場合があります。この場合、テストのグループをテストスイートに構成できます。 JUnit 5でこれを行う方法については、チェックアウトしてください HowToProgram.xyzのJUnit5の記事 、およびJUnitチームの JUnit4のドキュメント 。
JUnit 5は、静的ではないネストされた内部クラスを使用して、テスト間の関係をより適切に示す機能を追加します。これは、Jasmine forJavaScriptのようなテストフレームワークでネストされた記述を使用したことがある人には非常によく知られているはずです。内部クラスには@Nested
の注釈が付けられていますこれを使用します。
@DisplayName
アノテーションもJUnit5の新機能であり、テストメソッド識別子に加えて表示される文字列形式でレポートのテストを記述できます。
@Nested
および@DisplayName
互いに独立して使用でき、一緒に使用すると、システムの動作を説明するより明確なテスト結果を提供できます。
ザ・ ハムクレストフレームワーク は、それ自体はJUnitコードベースの一部ではありませんが、テストで従来のアサートメソッドを使用する代わりの方法を提供し、より表現力豊かで読みやすいテストコードを可能にします。従来のassertEqualsとHamcrestassertThatの両方を使用した次の検証を参照してください。
//Traditional assert assertEquals('Hayden, Josh', displayName); //Hamcrest assert assertThat(displayName, equalTo('Hayden, Josh'));
Hamcrestは、JUnit4と5の両方で使用できます。 Hamcrestに関するVogella.comのチュートリアル 非常に包括的です。
記事 ユニットテスト、テスト可能なコードの書き方、そしてそれが重要な理由 クリーンでテスト可能なコードを書くためのより具体的な例をカバーしています。
自信を持って構築する:JUnitテストのガイド ユニットテストと統合テストへのさまざまなアプローチと、1つを選択してそれを維持することが最善である理由を検討します
ザ・ JUnit 4 Wiki そして JUnit5ユーザーガイド 常に優れた基準点です。
ザ・ Mockitoドキュメント 追加の機能と例に関する情報を提供します。
JUnitを使用したJavaの世界でのテストの多くの側面を調査してきました。 Javaコードベース用のJUnitフレームワークを使用したユニットテストと統合テスト、開発環境とビルド環境でのJUnitの統合、サプライヤーとMockitoでのモックとスタブの使用方法、一般的な規則とベストコードプラクティス、テスト対象、およびいくつかのテストについて説明しました。その他の優れたJUnit機能。
今度は、JUnitフレームワークを使用した自動テストの利点を巧みに適用、維持、および享受することに成長する読者の番です。