Given-When-Thenは、テストシナリオを記述するための構造パターンであり、BDDがドメイン駆動設計から借用し、Behaviour-Driven Development向けに適応したものです。この形式はシナリオを3つの論理部分(前提条件(Given)、アクション(When)、期待結果(Then))に分割します。Martin Fowler(2023)によると、Given-When-Thenは単なるテストの形式ではなく、実装開始前に要件分析とシナリオ設計を規律づける思考ツールです。
重要なポイント
Given-When-Thenは、Dan Northが2006年にBehavior-Driven Development手法の一部として初めて策定した動作記述パターンです。このパターンは、ランダムな順序で前提条件、アクション、検証が混在することが多い、構造化されていないテストシナリオ記述の問題を解決します。
このパターンの主なアイデアは、3つのブロック間の責務の分離です。各ブロックはシナリオの1つの側面(前の状態、イベント中、後の検証)のみを担当します。これにより、シナリオが読みやすく、検証可能で、自動化可能になります。Cucumberフレームワークの開発者による研究(2024年)によると、Given-When-Thenパターンに厳密に従ったシナリオは、新しいチームメンバーが理解するのに42%少ない時間で済みます。
Dan Northは、TDDにおけるテストの策定とTest-by-Example手法(Brian Marickが作成)から3部構成のアイデアを借用しました。Marickは、同時にテストとして機能する例(examples)を通じて要件を記述することを提案しました。Given-When-Thenはこのアイデアを形式化し、構造化されていない例を反復可能なパターンに変えました。
Given-When-Thenパターンは、Gherkinを使ったBDDシナリオだけでなく、JUnit、XCTestなどのフレームワークを使った一般的なユニットテストにも適用されます。テストを3つのブロックに分割するコード内のコメントは、テストベースの可読性を向上させる一般的な手法です。Googleはその著書『Software Engineering at Google』(2020年)でこのアプローチを推奨しています。
Given-When-Thenの各ブロックには、厳密に定義されたセマンティクスと記入ルールがあります。これらのルールに違反すると、自動化や理解が困難なシナリオになります。
Givenブロックは、テスト対象アクションを実行する前のシステムの状態を記述します。これには、既存のオブジェクト(ユーザー、注文、設定)、アクティブな状態(認証済み、ネットワーク接続済み)、データの初期値が含まれます。各Givenは検証可能でなければなりません。システムの状態がGivenと一致しない場合、シナリオをスキップするか、テスト環境を事前に準備する必要があります。
Whenブロックは、テスト対象の動作を開始する唯一のイベントを記述します。これはメソッド呼び出し、ボタンクリック、通知の受信、サーバーからの応答などです。重要なルールはシナリオにつき1つのWhenです。アクションのシーケンスを検証する必要がある場合は、Whenの連鎖ではなく、個別のシナリオを作成します。
// Given:テストデータを作成
val user = User(email = "test@example.com", balance = 500.0)
val product = Product(price = 150.0)
// When:アクションを実行
val result = PurchaseUseCase().buy(user, product)
// Then:結果を検証
assertEquals(PurchaseResult.Success, result)
assertEquals(350.0, user.balance)
Thenブロックは、システムが期待された状態に移行したことを検証します。これには、戻り値、オブジェクトの状態変更、外部サービス呼び出し(モック検証による)、UI変更が含まれます。各Thenブロックには複数の検証を含めることができますが、すべてが1つのアクションに関連しています。
Given-When-ThenとArrange-Act-Assert(AAA)は、同じ3部構成パターンの2つのバリエーションですが、対象とするユーザーが異なります。それらの違いを理解することで、特定のタスクに適切な形式を選択するのに役立ちます。
| 側面 | Given-When-Then | Arrange-Act-Assert |
|---|---|---|
| 起源 | BDD、ビジネス分析 | ユニットテスト |
| 言語 | 自然言語(Gherkin) | コード(Kotlin、Swift、Java) |
| 対象 | チーム全体+クライアント | 開発者 |
| 詳細レベル | 高レベル | 詳細 |
| 自動化 | Cucumber、SpecFlow | JUnit、XCTest、Mockito |
Given-When-Thenパターンは、クライアントやアナリストと議論するシナリオ(機能の受け入れ基準、ユースケース、回帰チェック)に最適です。Gherkin構文を使用すると、プログラミング知識がなくてもこれらのシナリオを記述できます。
Arrange-Act-Assertは、特定のメソッドやクラスを検証するユニットテストの自然な選択肢です。AAA形式は追加のフレームワークを必要とせず、任意のプログラミング言語で機能します。iOS開発では、AppleはXCTestのドキュメント(2024年)でAAAを推奨しています。
Androidアプリケーション向けのKotlinでのGiven-When-Thenの実践例を見てみましょう。最初の例はMockKを使用したショッピングカートのテストです。2つ目はプッシュ通知ロジックのテストです。
class CartTest {
fun `apply discount when total exceeds threshold`() {
// Given
val cart = Cart()
cart.addItem(Item("Laptop", price = 1000.0))
cart.addItem(Item("Mouse", price = 50.0))
val discount = DiscountCalculator(0.1)
// When
val total = discount.applyIfEligible(cart)
// Then
assertEquals(945.0, total)
assertTrue("Discount was not applied", total < 1050.0)
}
}
2つ目の例は、非同期コードを使用したGiven-When-Thenを示しています。ここではGivenがFirebase Cloud Messagingの状態を設定し、Whenがプッシュ通知の受信、Thenが処理の検証を行います。
class PushNotificationTest {
fun `handle push notification when app in background`() = runTest {
// Given
val prefs = mockk<SharedPreferences>()
every { prefs.getString("token", null) } returns "fcm-token-abc"
val handler = PushHandler(prefs)
// When
val data = RemoteMessage().apply {
putData("type", "order_update")
putData("order_id", "123")
}
val result = handler.handleNotification(data)
// Then
assertEquals(NotificationAction.OpenOrder("123"), result)
}
}
3つ目の例は、受け入れテストのコンテキストでGiven-When-Thenを示すGherkinでのBDDシナリオです:
Feature: User Authorization
Scenario: User cannot login with expired token
Given the user has an expired refresh token
When they try to access the protected profile screen
Then they should see the login screen
And the app should clear all cached data
Given-When-Thenを効果的に適用するには、いくつかの実証済みのプラクティスに従う必要があります。これらはシナリオの可読性、保守性、自動化を保証します。
厳格なルール:1つのシナリオ=1つのアクション。複数のWhenのシーケンスを検証する必要がある場合は、前の結果が次の前提条件となる複数のシナリオを作成します。これにより、シナリオが原子的で理解しやすくなります。
Givenは本質を記述すべきであり、具体的な数字ではありません。「GivenユーザーIvanov、残高500ルーブル」ではなく、「Given十分な残高を持つユーザー」とします。具体的なデータはExamplesテーブルを持つScenario Outlineに移動します。これにより、シナリオが普遍的で再利用可能になります。
Given-When-Thenシナリオを継続的インテグレーションパイプラインに統合することで、ドキュメントからリグレッションに対する保護に変わります。モバイルプロジェクトの各マージリクエストは自動的にBDDシナリオを実行し、少なくとも1つのシナリオが失敗した場合、マージをブロックします。
Android向けCucumberのBDDシナリオは、Gradleタスク./gradlew cucumberを介して実行されます。iOS(Quick/Nimble)の場合はxcodebuild testを介して実行されます。CIシステム(GitHub Actions、GitLab CI、Bitrise)では、BDDテストはエミュレーターまたは実機で実行されます。レポートはマネージャーにも理解可能なHTML形式で生成されます:緑のシナリオは合格、赤はステップを示した失敗です。
.featureファイルはコードとともにリポジトリに保存され、コードレビューを受けます。アナリストは開発開始前に(BDDファーストで)新しいシナリオを含むマージリクエストを作成します。開発者はこれらのシナリオをグリーンにするためにステップ定義と実装を作成します。すべてのシナリオが合格すると、機能は完了です。Gojko Adzicの著書『Specification by Example』(2011年)で説明されているこのアプローチは、要件を実行可能な成果物に変換します。
よくある質問
構造的にははい、同じ3部構成パターンです。違いは対象ユーザーにあります:Given-When-Thenはビジネス言語指向でBDDでGherkinとともに使用され、Arrange-Act-Assertはユニットテスト用の技術的形式です。選択はコンテキストとチームによって異なります。
制限はありませんが、1つのThenにつき3〜5個以下の検証が推奨されます。検証が多い場合、シナリオが1つのアクションでチェックしすぎている可能性があります。異なるThenを持つ複数のシナリオに分割してください。
いいえ。このパターンは任意のテストフレームワークで使用でき、コメントや空行でテストを3つのブロックに分割するだけです。GherkinはCucumberやSpecFlow用の.featureファイル形式でシナリオを書く場合にのみ必要です。
繰り返しの前提条件はBackground(Gherkin)または@Beforeメソッド(JUnit)に抽出することをお勧めします。前提条件が複雑な場合は、Builderパターンを使用してテストデータを作成します。これにより、Givenを短く読みやすく保てます。
いいえ。Whenはアクションを記述する必須ブロックです。シナリオがアクションなしで状態のみを検証する場合(例:「アプリケーション読み込み時にデータがキャッシュされるべき」)、Whenはトリガーを記述します:「アプリケーションが起動するとき」。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。