XCTest-Gherkin 0.21.2

XCTest-Gherkin 0.21.2

测试已测试
语言语言 SwiftSwift
许可证 Apache-2.0
发布上次发布2021年3月
SPM支持SPM

Sam DeanSam Dean维护。



  • Sam Dean

XCTest-Gherkin

CI Status Version License Platform

XCTest+Gherkin

在net-a-porter,我们传统上使用Cucumber和Appium进行UI测试,效果不错并且完成了任务。然而,它有几个缺点;它需要掌握另一种语言(在我们的情况是Ruby),它需要在我们的CI堆栈上更多信息(cucumber、node、appium、ruby、gems等),运行速度较慢,并且似乎总是落后于最新Xcode技术。这些缺点单独看都不是决定性的,但加在一起使得UI测试比我们想象的更繁琐。

本项目的目标包括

  1. 提高编写UI测试的速度并减少技术开销,最终目标是开发者和测试人员在编写单元测试时一起编写UI测试。这些测试将在每个合并时由我们的CI运行,因此它们必须快速。
  2. 不丢失现有的测试覆盖率。我们使用Appium有一段时间了,因此我们建立了一组良好的特性文件,覆盖了大部分功能,我们不想丢失这些。

目标#1很容易实现;我们只是使用Xcode中内置的技术,这样测试和应用程序之间就有了一个共同的技术,使用开发者和技术人员都了解的通用语言。

目标#2更麻烦——我们需要保留我们的 featsuer 文件,并设法将其迁移到新系统中。我们的测试结构尽可能与Cucumber结构相似,以降低学习曲线;我们已经在要求测试员学习一门新语言了!

解决方案是扩展 XCTestCase 以在编写测试时支持 Gherkin 语法,如下所示

功能

import XCTest
import XCTest_Gherkin

class testAThingThatNeedsTesting: XCTestCase {
    func testBasicSteps() {
        Given("A situation that I want to start at")
        When("I do a thing")
        And("I do another thing")
        Then("This value should be 100")
        And("This condition should be met as well")
    }
}

这是一个有效的测试用例,应该运行在 Xcode 中,失败行会被突出显示,并在测试检查器面板中显示测试结果。重要的是要保持对哪个测试失败以及原因的可见性!

步骤定义

下一步是为每个这些步骤编写步骤定义。这里给出了两个例子

class SomeStepDefinitions : StepDefiner {  
    override func defineSteps() {
        step("A situation that I want to start at") {
            // Your setup code here
        }

        step("This value should be ([0-9]*)") { (matches: [String]) in
            let expectedValue = matches.first!
            let someValueFromTheUI = /* However you want to get this */
            XCTAssertEqual(expectedValue, someValueFromTheUI)
        }
    }
}

这些步骤通过正则表达式(使用 不区分大小写NSRegularExpression)匹配,并返回捕获组(如果有的话)。第二个步骤将捕获测试末尾的数字并与当前 UI 的状态进行比较。

有一些方便的步骤方法版本,可以为您提取第一个匹配项

step("This value should be ([0-9]*)") { (match: String) in
    XCTAssertEqual(expectedValue, match)
}

step("This value should be between ([0-9]*) and ([0-9]*)") { (match1: String, match2: String) in
    let someValue = /* However you want to get this */
    XCTAssert(someValue > match1)
    XCTAssert(someValue < match2)
}

捕获值类型

在包含捕获值的步骤定义中,您可以使用任何符合 MatchedStringRepresentable 协议的类型。《String》、《Double》、《Int》和《Bool》类型已符合此协议。您还可以通过将自定义类型符合《CodableMatchedStringRepresentable》来匹配您的自定义类型。这要求类型实现仅《Codable》协议方法,而《MatchedStringRepresentable》的实现由库提供。

struct Person: Codable, Equatable {
  let name: String
}
extension Person: CodableMatchedStringRepresentable {
}

step("User is logged in as (.+)") { (match: Person) in
    let loggedInUser = ...
    XCTAssertEqual(loggedInUser, match)
}

func testLoggedInUser() {
    let nick = Person(name: "Nick")
    Given("User is loggeed in as \(nick)")
}

命名捕获组

在 iOS 11 和 macOS 10.13 中,您可以使用命名捕获组来改进控制台和活动日志。组名将转换为可读形式,并替换它捕获的步骤表达式子串。这对于您在上一节中描述的将自定义类型用作步骤参数特别有用。

没有命名捕获组,此类测试

step("User is logged in as (.+)") { (match: Person) in ... }

func testLoggedInUser() {
    let nick = Person(name: "Nick")
    Given("User is loggeed in as \(Person(name: "Nick"))")
}

将产生以下日志

step User is loggeed in as {"name":"Nick"}

使用命名捕获组,步骤定义可以看起来像这样(注意,match 现在是 StepMatches&لت Person>

step("User is logged in as (?<aRegisteredUser>.+)") { (match: StepMatches<Person>) in ... }

同样的一次测试会产生日志

step User is logged in as a registered user

在步骤实现中,您将使用组的名称来访问匹配的值,例如 match["aRegisteredUser"]。您可以通过索引访问所有匹配的值(包括未命名的组匹配),索引从0开始,例如 match[0]。因此,您可以有多个命名组,并且可以与未命名的组混合使用。

示例和功能概述

如果您想用一组数据测试相同的情况,Gherkin 允许您为测试指定示例输入。我们已经在之前的测试中使用了它,所以我们也需要在这里处理它!

func testOutlineTests() {
    Examples(
        [ "name", "age" ],
        [ "Alice", "20" ],
        [ "Bob", "20" ]
    )

    Outline {
        Given("I use the example name <name>")
        Then("The age should be <age>")
    }
}

这将运行两次测试,一次使用值 Alice,20,一次使用值 Bob,20

使用 ExamplesOutline 函数的简单方法是在 Outline 之前调用 Examples。但在 Gherkin 特性文件中,示例总是放在场景概述之后。如果您想保持本机测试的这种顺序(并且不关心一点点奇怪的 Xcode 缩进),则可以提供示例,在定义概述后通过尾随闭包或显式 Examples 参数进行。

func testOutlineTests() {
    Outline({
        Given("I use the example name <name>")
        Then("The age should be <age>")
    }) {
        [
            [ "name" , "age", "height" ],
            [ "Alice", "20" , "170"    ],
            [ "Bob"  , "20" , "170"    ]
        ]
    }
        
    // or
    
    Outline({
        Given("I use the example name <name>")
        Then("The age should be <age>")
    }, examples: 
        [
            [ "name" , "age", "height" ],
            [ "Alice", "20" , "170"    ],
            [ "Bob"  , "20" , "170"    ]
        ]
    )
}

背景

如果您在每个场景中重复相同的步骤,则可以将它们移动到 BackgroundBackground 在每个场景(实际上是在第一次场景步骤执行之前)或概述通过之前运行,但在 setUp() 之后。您可以在 Background 中设置尽可能多的步骤。

class OnboardingTests: XCTestCase {

    func Background() {
        Given("I launch the app")
    }

    func testOnboardingIsDisplayed() {
        Then("I see onboarding screen")
    }

    func testOnboardingIsDisplayedEachTime() {
        Examples([""], ["1"], ["2"])

        Outline {
            Then("I see onboarding screen")
            And("I kill the app")
        }
    }

}

页面对象

内置的 PageObject 类型可以用作您自己的页面对象的基类型。它将断言其 isPresented()(您应该覆盖),当创建其实例时返回 true。它还定义了一个 name 属性,默认情况下是类型名称,不带 PageObject 后缀(如果有的话)。

PageObject 还附带了一些由 CommonPageObjectsStepDefiner 定义的自定义步骤,这些步骤用于验证该页面对象是否显示,格式为 I see %@I should see %@it is %@,可选参数前面使用 the

处理错误 / 调试测试

缺失的步骤

如果在您的功能文件中找不到步骤定义,则扩展会输出所有可用步骤的列表,然后终止测试,如下所示:

steps
-------------
/I have a working Gherkin environment/  (SanitySteps.swift:17)
/I use the example name (?:Alice|Bob)/  (SanitySteps.swift:38)
/The age should be ([0-9]*)/  (SanitySteps.swift:44)
/This is another step/  (SanitySteps.swift:33)
/This step should call another step/  (SanitySteps.swift:28)
/This test should not ([a-zA-Z0-9]*)/  (SanitySteps.swift:23)
-------------
XCTestCase+Gherkin.swift:165: error: -[XCTest_Gherkin_Tests.ExampleFeatures testBasicSteps] : failed - Step definition not found for 'I have a working Pickle environment'

模糊的步骤

有时,多个步骤可能包含相同的文本。库将匹配它认为正确的步骤,但它可能会出错。例如,如果您有这些步骤定义:

step("email button") { ... }
step("I tap the email button") { ... }

当您尝试运行此Given

func testStepAnchorMatching() {
    Given("I tap the email button")
}

它可能匹配到“邮件按钮”步骤,而不是“我点击邮件按钮”步骤。为解决这个问题,有两种选择。

  1. 您可以将精确的字符串字面量传递给步骤定义,而不是使用常规方法,该方法将所有内容视为正则表达式。
step(exactly: "I tap the email button")

这将仅匹配精确文本“我点击邮件按钮”。此字符串中的任何正则表达式特殊字符都将进行精确匹配。

  1. 您可以使用 ^$ 将正则表达式锚定于字符串的开始和结束,如下所示
step("^email button$") { ... }
step("I tap the email button") { ... }

现在,“我点击邮件按钮”不匹配第一个步骤。

此方法适用于需要匹配模糊步骤,但不能使用方法(1)的场合,因为您还需要使用正则表达式的其他功能(例如,模式匹配等)

屏幕截图

有失败UI测试的截图非常有用,这可以通过以下方式进行配置

XCTestCase.setAutomaticScreenshotsBehaviour([.onFailure, .beforeStep, .afterStep],
                                            quality: .medium,
                                            lifetime: .deleteOnSuccess)

安装

CocoaPods

XCTest-Gherkin 可通过 CocoaPods 获取。要安装,只需将以下行添加到您的 Podfile 中

pod 'XCTest-Gherkin'

然后运行 pod install

Carthage

XCTest-Gherkin 还可通过 Carthage 获取。要安装,只需将以下行添加到您的 Cartfile

github "net-a-porter-mobile/XCTest-Gherkin" == 0.13.2

然后运行 carthage bootstrap --platform iOS。生成的框架名为 XCTest_Gherkin.framework

Swift Package Manager

在您的 Xcode 项目中,通过菜单“文件”->“Swift 包”->“添加包依赖...”添加 XCTest-Gherkin。

注意:使用 Swift Package Manager 与 XCTest-Gherkin 结合使用时,Xcode 12 和 Swift 5.3 是最低要求。

配置

无需进行配置。

示例

在 pod 的 Example 项目中存在可用的示例 - 只需运行测试并查看结果!

原生特性文件解析

为了帮助从原生特性文件(我们之前测试套件中有许多此类文件)迁移到 Swift 的工作中,能够解析当前特性文件而无需将它们修改为它们的 Swift 等价物将非常方便。

当产品负责人刚开始编写特性文件时,这也很有用,因为他们知道 Given/When/Then 语法,但不是 Swift 开发者:

如果您在 podfile 中包含 Native 子 pod

pod 'XCTest-Gherkin/Native'

您将包括解析真正的 Gherkin 语法特性文件的能力,并且库能够从它们创建运行时测试。

这个例子在 Example/ 项目的这个 pod 中。查看 ExampleNativeTest 类 - 您只需要指定包含文件夹,该文件夹中的所有特性文件都将被读取。

这种方法的优点显而易见;您可以快速运行现有的特性文件,并可以快速开始。缺点是因为测试是在运行时生成的,无法在 Xcode 中单独运行,因此调试比较困难。我会用这个来在 Xcode 中开始测试,但如果情况变得复杂,我会将特性文件转换为原生 Swift 测试,然后在那里进行调试。

特性文件本地化

您可以使用多种语言编写的特性文件。要将特性文件的语言设置为指定语言,请在特性文件的第一行添加 # language: en 以及相应的语言代码。默认情况下,使用的是英文本地化。您可以在 gherkin-languages.json 文件中查看所有可用的本地化选项,也可以通过代码使用 NativeTestCase.availableLanguages 属性。以下是一个俄语特性文件的示例

# language: ru
Функция: Разбор простого функционального файла

    Сценарий: Это очень простой пример успешного сценария
        Допустим Я имею рабочее окружение Gherkin
        Тогда этот тест не должен завершиться ошибкой

免责声明

这里的 Gherkin 语法解析器并不是真正适合生产环境 - 它绝对不是一个验证器,并且可能会非常愿意解析格式不正确的 Gherkin 文件。它正在解析的特性文件假定构建得相当好。这个子 pod 的目的是帮助从旧特性文件迁移到 Swift 的方式,所以这就是它能做的所有事情。如果您想改变这个,请随时提交拉取请求:

XCTest+Gherkin at net-a-porter

我们使用这个扩展与KIF一起进行我们的UI测试。对于单元测试,我们只使用XCTest。KIF在我们这里工作得很好,并且比我们的先前测试套件快得多。

我们把对KIF的调用放在我们的步骤定义中,这恰好与我们之前的Cucumber实现非常相似,使得迁移更容易。

作者

Sam Dean, [email protected]

许可协议

详细信息请参见LICENSE - 这是Apache许可协议。