1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

KotlinAdvent Calendar 2024

Day 15

Cucumber JVM, Android の BDD

Posted at

この記事はKotlin Advent Calendar 2024 15日目の記事です。

2024/11/26 Kotlin愛好会 にて お話ししたCucumber を記事にします。

Cucumber

  • 振舞駆動開発; behavior driven development, BDD をサポートするツール
  • Featureファイルに記述した自然言語のテストケース テスト関数を生成してくれるような仕組み
  • サポート言語は多岐にわたる

Featureファイル

例えば

Feature: Belly
  Scenario: a few cukes
    Given I have 42 cukes in my belly
    When I wait 1 hour
    Then my belly should growl

Gherkin記法でテストケースを記述するファイル用意して, BDD-jvm に食わせると以下が吐き出されるので それにテストコードを書いていく形

Given("I have {int} cukes in my belly", (Integer int1) -> {
    // Write code here that turns the phrase above into concrete actions
    throw new io.cucumber.java8.PendingException();
});
When("I wait {int} hour", (Integer int1) -> {
    // Write code here that turns the phrase above into concrete actions
    throw new io.cucumber.java8.PendingException();
});
Then("my belly should growl", () -> {
    // Write code here that turns the phrase above into concrete actions
    throw new io.cucumber.java8.PendingException();
});

Gherkin記法

この記法自体も Cucumberのコミュニティが メンテナンスしてる
BDD に沿った記述のしやすい記法

Feature: Guess the word

  # The first example has two steps
  Scenario: Maker starts a game
    When the Maker starts a game
    Then the Maker waits for a Breaker to join

  # The second example has three steps
  Scenario: Breaker joins a game
    Given the Maker has started a game with the word "silky"
    When the Breaker joins the Maker's game
    Then the Breaker must guess a word with 5 characters

ざっくり以下

Feature: 機能名の記述

	Scenario: テストシナリオの記述、ビジネスルールの記述 etc
	  Given テストの前提条件
	  When アクションやイベントを記述
	  Then 期待される結果 「~するべき」 の部分

全ては紹介しきれないが 他にも色々👇

例えば

Android では Instrumentation Test をサポートしている

認証したらアプリのサービスを確認できる みたいなシナリオを書いてみると

Feature: ログイン
  ユーザストーリー: ログインするとホームを確認できる # descriptionが書ける

  Scenario: ログインするとホームを確認できる
    Given ログインID "anpanman" パスワード "password"
    When ログインした
    Then ホーム画面が確認できる

  Scenario: ログインに失敗する
    Given ログインID "aaaaaaa" パスワード "passward"
    When ログインしようとする
    Then ホームが表示されない
@HiltAndroidTest
class LoginScenario @Inject constructor(
    private val composeRuleHolder: ComposeRuleHolder
): SemanticsNodeInteractionsProvider by composeRuleHolder.activityComposeRule {

    private val rule = composeRuleHolder.activityComposeRule
    
    @Given("ログインID {string} パスワード {string}")
    fun ログインid_パスワ(string: String, string2: String) {
        rule.waitUntilAtLeastOneExists(
            hasTestTag("login-screen-content"),
            3000
        )
        onNodeWithTag("id-textfield")
            .performTextInput(string)
        onNodeWithTag("pass-textfield")
            .performTextInput(string2)
    }
    
    @When("ログインした")
    fun ログインした( ) {
        onNodeWithTag("login-button")
            .performClick()
    }
    
    @Then("ホーム画面が確認できる")
    fun ム画面が確認できる( ) {
        rule.waitUntilAtLeastOneExists(
            hasTestTag("home"),
            5000
        )
        onNodeWithTag("home").assertExists()
    }

    @When("ログインしようとする")
    fun ログインしようとする( ) {
        onNodeWithTag("login-button")
            .performClick()
    }

    @Then("ホームが表示されない")
    fun ムが表示されない( ) {
        rule.waitUntilAtLeastOneExists(
            hasTestTag("login-circular"),
            5000
        )
        rule.waitUntilDoesNotExist(
            hasTestTag("login-circular"),
            5000
        )
        onNodeWithTag("home").assertDoesNotExist()
    }
}

セットアップは少々Stepあり

Cucumberは テストスイートを CucumberOptionsで定義して Cucumberがそれを見つけて実行するような仕組み
composeRule や ActivityTestRule はそのままだとテストファイルに宣言しても使えないため RuleHolder的なコンテナをHiltで注入する仕組みになっている。

    implementation(libs.hilt.android) // androidTestImplementationのみでは警告
    ksp(libs.hilt.compiler)
    kspAndroidTest(libs.hilt.compiler)
    androidTestImplementation(libs.hilt.testing)
    androidTestImplementation(libs.cucumber.android)
    androidTestImplementation(libs.cucumber.android.hilt)

エントリとなるテストスイートを 専用の CucumberOptionアノテーションで作成する。Hiltのテストのセットアップもここで行う。

@CucumberOptions(
    features = [ "features" ],
    glue = ["com.example.examplecucumberandroid"]
)
class MyAndroidCucumberTestRunner : CucumberAndroidJUnitRunner() {
    override fun newApplication(
        cl: ClassLoader?,
        className: String?,
        context: Context?
    ): Application {
        return super.newApplication(
            cl,
            HiltTestApplication::class.java.name,
            context
        )
    }
}

前述の コンテナを準備 @WithJunitRuleorg.junit.Ruleを使用するためクラスを作る

@WithJunitRule
@Singleton
class ComposeRuleHolder @Inject constructor() {
    @get:Rule(order = 1)
    val composeRule = createComposeRule()

    @get:Rule(order = 1)
    val activityComposeRule = createAndroidComposeRule<MainActivity>()
}

あとは 前述通り

@HiltAndroidTest
class LoginScenario @Inject constructor(
    private val composeRuleHolder: ComposeRuleHolder
): SemanticsNodeInteractionsProvider by composeRuleHolder.activityComposeRule {

    private val rule = composeRuleHolder.activityComposeRule

    @Given("ログインID {string} パスワード {string}")
    fun ログインid_パスワ(string: String, string2: String) {...

うまみはあるか?

以下目的ではいいのかもしれない

  • 仕様側と 開発側が そこそこ明確に分かれて、仕様と実装の間に 共通言語となるドキュメントが欲しい

  • iOS , Android みたいな 各プラットフォームで共通のテスト仕様 を 書く

  • BDD , TDD 自体を推進したい

    • BDD で決めた振る舞い ベースのテスト仕様から TDD に繋げて プロダクションコードを設計する みたいな手法があるが それの一部で使うみたいなイメージ
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?