BDDSwift

BDDSwift 是一个 类型安全编译器强制执行 的 BDD Given/When/Then 语法实现,用于编写描述性的、人类可读的 XCTest 测试。

BDDSwift 使用 Swift resultBuilder强制执行场景中步骤的顺序;一个场景必须以 Given 步骤开始,必须包含 When 步骤,并且必须以 Then 步骤(或跟在 Then 步骤之后的 And 步骤)结束。

例如,这是有效的

Scenario("My amazing scenario") {
    Given(the: appIsInSomeState)
    When(I: performSomeAction)
    Then(the: appIsInADifferentState)
}

这也是有效的

Scenario("My amazing scenario") {
    Given(the: appIsInSomeState)
    And(somethingElseIsSetUp)
    When(I: performSomeAction)
    And(I: performSomeOtherAction)
    Then(the: appIsInADifferentState)
    And(someOtherStateHasChanged)
}

但这无效,并且无法编译

Scenario("My amazing scenario") {
    When(the: appIsInSomeState)
    Given(I: performSomeAction)
    Then(the: appIsInADifferentState)
}

这也不行

Scenario("My amazing scenario") {
    Given(the: appIsInSomeState)
    And(I: performSomeAction)
    And(the: appIsInADifferentState)
}

每个步骤都接受一个函数作为参数,该函数由场景按照步骤的正确顺序执行。

为了使步骤读起来更像一个句子,您可以为不带参数的函数省略 ()

When(I: tapAButton)

但是,如果您需要调用一个带有参数的函数,则每个步骤都有一个自动闭包,以保持一切的可读性

When(I: select(tab: .profile))

如果您真的需要,您也可以传递一个闭包

When {
    // Some code here
}

但是,我建议您保持代码的可读性,并将所有代码放入命名良好的函数中,这也有助于提高可重用性,尤其是在 UI 测试中。

用法

首先,在测试函数中创建一个 Scenario,并为其提供描述

func test_happyPath() {
    Scenario("Launching the app and logging in shows home screen") {
        // This won't compile yet because you have to add some steps!
    }
}

然后构建场景涵盖的步骤

func test_happyPath() {
    Scenario("Launching the app and logging in shows home screen") {
        Given(the: appIsLaunched)
        When(I: logIn)
        Then(I: see(.homeScreen))
    }
}

private func appIsLaunched() {
    // Code to launch the app
}

private func logIn() {
    // Code to log in
}

private func see(_ screen: Screen) {
    // Code to assert home screen is showing
}

每个场景应该只有一个 GivenWhenThen 步骤(按照该顺序,否则它将无法编译!),但是如果您需要添加额外的上下文、等待特定状态或需要执行对场景的每个阶段都很重要的其他工作,则可以在每个步骤之后添加 And 步骤

func test_happyPath() {
    Scenario("Launching the app and logging in shows home screen") {
        Given(the: appIsLaunched)
        And(I: see(.loginScreen))
        When(I: logIn)
        Then(I: see(.homeScreen))
        And(the: lastAnalyticsEventsTracked(are: .viewLoginScreen, .userLogIn, .viewHomeScreen))
    }
}

private func appIsLaunched() {
    // Code to launch the app
}

private func logIn() {
    // Code to log in
}

private func see(_ screen: Screen) {
    // Code to assert home screen is showing
}

private func lastAnalyticsEventsTracked(are events: AnalyticsEvent...) {
    // Code to assert correct analytics have been tracked
}

请注意在上面的代码示例中,我们混合使用了通过引用传递函数(即不编写 ())和调用带有参数的函数,所有这些都同时保持了很强的可读性。

我建议在您使用不带参数的函数时,尽可能通过引用传递函数,这是编写步骤最易读的方式,但是当您调用带有参数的函数时,BDDSwift 仍然通过使用自动闭包使事情变得可读。

✨ 趣闻:自动闭包将您的代码包裹在不可见的 curly braces 中,这就是您可以在不包裹在 { ... } 中的情况下将函数传递到步骤初始化器的原因!

此外,您可能会注意到初始化器参数为您提供了 the:I: 等选项。这意味着您可以从您的各个函数签名中删除这些单词,但如果您希望自己将此添加到函数名称中,则取决于您,例如

Given(theAppIsLaunched)
When(iLogIn)
Then(iSeeScreen(.profile))

但这往往读起来不太像正常的句子,而这正是 BDD 的主要好处之一;人类可读的行为描述。

与 XCUI 测试集成

如果您在 XCUI 测试中使用 BDDSwift,那么您将自动获得添加到测试日志中的额外日志,形式为 XCTActivity,这有助于您查明故障发生的位置以及为到达那里而采取的步骤。

由于对于 XCUI 测试来说,每个测试用例有一个长测试函数是非常常见的(并且推荐用于并行测试),BDDSwift 还可以帮助您将长测试分解为逻辑场景,这使得它们更易于阅读、扩展和维护。

func test_happyPath() {
    Scenario("Launching the app and logging in shows home screen") {
        Given(the: appIsLaunched)
        And(I: see(.loginScreen))
        When(I: logIn)
        Then(I: see(.homeScreen))
    }
    Scenario("Tapping on Settings tab shows settings") {
        Given(I: see(.homeScreen))
        When(I: tap(tab: .settings))
        Then(I: see(.settingsScreen))
    }
    Scenario("Tapping on Log Out button shows log in screen") {
        Given(I: see(.settingsScreen))
        When(I: tap(.logout))
        Then(I: see(.loginScreen))
    }
}

✨ 趣闻:Xcode 按测试用例而不是测试函数并行化测试。这意味着它将启动多个模拟器并在每个模拟器上运行不同的测试用例。如果您的测试用例中有很多单独的测试函数,Xcode 将无法拆分它们并在测试用例中并行运行它们;为了避免这种情况,只需为每个测试用例创建一个测试函数,以便 Xcode 可以并行运行它们。