Swerkin

Swerkin 是一个仪器测试框架,具有 5 个主要目标

  1. 扩展现有的开源 iOS 仪器库。

    目前,Swerkin 扩展了 KIF - Keep it Functional。但在未来的版本中,可能会扩展 XCUITest 和/或 Earl Grey

  2. 从这些框架构建的测试使用类似 Gherkin 的语法 (Given/When/Then)。

    这使得测试的设置、测试正在执行的操作以及测试正在验证的内容易于阅读和理解。

    类似 Gherkin 的语法使得开发人员和非开发人员之间的协作更容易构建测试场景

  3. 提供预定义步骤定义的目录,以及用 Swift 编写新步骤定义的模式。

    创建测试场景的能力应该对开发人员和非开发人员都容易,但应该为开发人员提供熟悉的体验;包括使用自动完成来查找步骤、用 Swift 编写测试场景,以及能够在 XCode 中调试步骤。

  4. 能够测试单个屏幕,而无需浏览整个应用程序。

    这允许有针对性的测试,并减少了应用程序深层屏幕的测试时间。

  5. 能够以易于阅读和简洁的方式测试整个流程。

    除了有针对性的测试之外,保留测试整个流程的能力,让您拥有两全其美的优势。为流程测试使用类似 Gherkin 的语法,可以更好地理解测试设置及其验证。

如何贡献

请参阅 贡献文件,了解如何为此框架做出贡献。

许可证

Swerkin 在 MIT 许可证下可用。有关更多信息,请参见 LICENSE 文件

Swerkin 功能

Swerkin 框架有 3 个主要组件:基本测试用例、步骤定义和屏幕对象。

BaseTestCase

BaseTestCase 是从该框架创建的每个测试类的基础。它提供了每个测试类中的测试可以利用的多个功能。

  1. 测试标签:能够向测试类和测试函数添加特定标签,以构建动态测试套件。

  2. TestInfo:用于存储每个测试所需的特定数据的字典。

  3. 超时:(可配置)

    • testTimeout:整个测试失败前的超时时间(默认值:60 秒)
    • waitingTimeout:等待条件为真的超时时间(默认值:2 秒)
    • validationTimeout:验证失败前的超时时间(默认值:2 秒)
  4. Preconditions:一个字典,用于保存设置数据,可用于确定如何执行流程和/或端到端测试。

  5. CurrentScreen:测试开始时呈现的屏幕。

  6. ScreenPresenter:一个特定的类,它提供了注册所有被测屏幕对象的能力,并可以在需要时返回特定屏幕。

  7. Swerkin 对象:包括 Given / When / Then / And,以提供类似 Gherkin 的体验,如果需要其他 Gherkin 语法,可以进一步扩展。

步骤定义

Swerkin 步骤定义分为三种类型

  1. Setup (设置)
  2. Action (操作)
  3. Assertion (断言)

SETUP (设置):

设置步骤定义用于设置和渲染被测屏幕。

//Sets the current screen in the test case to a given presentable screen
func IAmOnScreen(screen: PresentableScreen)
//Render a given presentable screen from the system under test
func IRender(screen: PresentableScreen) 
//Navigate from one screen to another via a set of step definitions
func INavigate(fromScreen: PresentableScreen, 
               toScreen:PresentableScreen) 

ACTION (操作):

操作步骤定义用于使用其可访问性属性(ID、标签、特征等)与元素交互。

//Touch button with given accessibility identifier
public func ITouchButton(_ buttonId: String) 
//Touch button with given accessibility label
public func ITouchButton(withLabel buttonLabel: String)
//Enter text into a text field with a given accessibility identifier
public func IEnterIntoTextField(_ id: String, text: String)

ASSERTION (断言):

断言步骤定义用于使用其可访问性属性(ID、标签、特征等)验证元素。

//Verifies a UIButton exists with the given accessibility identifier
public func IShouldSeeButton(_ buttonId: String)
//Verifies a UIButton exists with the given accessibility label
public func IShouldSeeButton(withLabel buttonLabel: String)
//Verifies a UITextField with a given accessibility identifier contains specific text  
public func IShouldSeeTextField(_ textFieldId: String, 
                                 withText text: String)

Screens (屏幕)

屏幕对象

每个屏幕都是Viewable(可查看的)、Assertable(可断言的)、Touchable(可触摸的)、Renderable(可渲染的)和Navigable(可导航的)。每个屏幕包括

  1. 对当前测试用例的引用
  2. 用于标识屏幕的唯一特征
  3. 屏幕的名称
  4. 渲染屏幕的方法
  5. 入口点列表,以辅助在流程测试期间导航到屏幕

PresentableScreen & ScreenProvider

PresentableScreen 是一个协议,用于定义应用程序中可呈现屏幕的枚举。

PresentableScreen 是屏幕对象的一个抽象,用于适应分解为单独功能模块的工作空间。所有被测的可呈现屏幕都可以在核心模块中定义,即使屏幕对象在工作空间中的单独功能模块中定义也是如此。

ScreenProvider 是一个类,给定一个 PresentableScreen,可以返回 Screen 类型或 Screen 对象。如果一个工作空间被分解为多个功能模块,则每个模块将为其模块中定义的屏幕对象定义自己的 ScreenProvider。

ScreenPresenter

ScreenPresenter 是一个类,它实现了 ScreenProvider 的注册,以便所有屏幕都可用于测试。

该类还提供了一个方法,给定一个 Presentable Screen,返回一个特定的 Screen Provider 对象,以及一个方法,给定一个 Presentable Screen,返回一个特定的 Screen 对象。

ScreenRenderer

ScreenRenderer 是一个协议,用于从被测系统中创建屏幕。

安装

Swerkin 可以通过 CocoaPods 获得。

要安装它,只需将以下内容添加到您的 Podfile 中

target 'Your Apps' do
  ...
end

target 'Acceptance Tests' do
  pod 'Swerkin'
end

添加后,运行 pod install 完成安装

实现

必须实现三个组件

测试核心类

屏幕类

我们建议创建一个实现 Screen 协议的基本屏幕类,以便可以提供默认值。

class ExampleBaseScreen: Screen {
    final var test: BaseTestCase
    
    final let renderer: ScreenRenderer = ExampleScreenRenderer()

    public required init(testCase: BaseTestCase) {
        self.test = testCase
    }

    final var testName: String { return test.name }

    var trait: String { return "" }
    var name: String { return "" }
    func create() -> UIViewController { UIViewController() }
    func renderScreen() {}
    func entryPathSegments() -> [PathSegment] {
        return []
    }
}

另一种选择是每个 Screen 对象都应该实现 Screen 协议。

PresentableScreen 枚举

创建一个枚举,其中包含应用程序中要测试的每个屏幕的 case。

public enum ExamplePresentableScreen: String, PresentableScreen {
    case buttonScreen
    case dropDownScreen
    case endToEndScreen
    case homeScreen
    case swipeScreen
    case tappableScreen
    case textFieldScreen
    case waitToSeeScreen
    case tableViewScreen
    
    public var rawValue: String {
        get {
            return String(describing: self).capitalized
        }
    }
}

ScreenProvider 类

每个 ScreenProvider 类都应从 ScreenProvider 继承,并覆盖函数 screen 和 typeMarker,以便将 Presentable Screens 转换为它们各自的 Screens。

public class ExampleScreenProvider: ScreenProvider<ExamplePresentableScreen> {
    public var testReference: BaseTestCase! = nil

    public required init(testCase: BaseTestCase?) {
        self.testReference = testCase
    }
    
    public override func screen(for screen: ExamplePresentableScreen) -> Screen? {
        switch screen {
        case .buttonScreen:
            return ButtonScreen(testCase: self.testReference)
        case .dropDownScreen:
           return DropdownScreen(testCase: testReference)

        ...
            
        }
    }

    public override func typeMarker(for screen: ExamplePresentableScreen) -> Screen.Type? {
        switch screen {
        case .buttonScreen:
            return ButtonScreen.self
        case .dropDownScreen:
            return DropdownScreen.self

        ...

        }
    }
}

Screen Renderer 类

ScreenRenderer 类应该实现 ScreenRender 协议,并为函数 screen 提供一个实现。该函数应该包含三个关键元素

  1. 创建被测屏幕(很可能是一个 ViewController)
  2. 将被测屏幕添加到导航堆栈的顶部
  3. 验证屏幕上的一个独特元素(特征)是否按预期出现
class ExampleScreenRenderer: ScreenRenderer {
    func screen(_ screenObject: Screen, didRenderWithAuth isAuth: Bool) {
       guard let screenObject = screenObject as? ExampleBaseScreen else { return }
        
        if isAuth {
            //Add code that is special to your app when the user is authenticated
        }
        
        //Navigation code to render the ViewController and add it to the stack to navigate directly to it
        if let navigationController = UIApplication.shared.topNavigationController() {
            navigationController.pushViewController(screenObject.create(), animated: false)
        }
        screenObject.viewTester.waitForAnimationsToFinish()
        screenObject.waitForElement(withIdentifier: screenObject.trait)
    }
}

测试用例类

为您的测试创建一个从 BaseTestCase 继承的 Test Case 类。在 setup 中注册所有的 ScreenProvider,并确保在 tearDown 中重置测试环境,以便每个测试都可以独立运行。

创建 Test Case 类时,也可以覆盖 BaseTestCase 中的默认值。

open class ExampleTestCase: BaseTestCase {

    open override func setUp() {
        super.setUp()

        self.screenPresenter.registerScreenProvider(ExampleScreenProvider(testCase: self), for: ExamplePresentableScreen.self)
    }

    open override func tearDown() {
        resetNavigation {
            self.navigateHome()
            self.waitForAnimationsToFinish()
        }

        super.tearDown()
    }
    ...
}

屏幕对象类

创建从您的基本屏幕对象继承或实现 Screen 协议的屏幕对象类。对于被测系统中的每个被验证的屏幕,应该有一个屏幕对象类。

每个屏幕对象都应该实现以下内容

测试类 (又名 Features / Specs)

创建从应用程序的基本测试用例类继承的测试类。在测试类中,使用类似 Gherkin 的语法,使用步骤定义的目录构建测试用例。

class Dropdown: ExampleTestCase {
    private let textField = DropdownScreen.View.textField.accessibilityIdentifier
    
    func testVerifySingleItem() {
        Given.IAmOnScreen(ExamplePresentableScreen.dropDownScreen)
        And.IRender(screen: ExamplePresentableScreen.dropDownScreen)
        When.ISetDropDown(textField, toValue: "Banana")
        Then.IShouldSeeTextField(textField, withText: "Banana")
    }

    func testVerifyWithLabelItem() {
        Given.IAmOnScreen(ExamplePresentableScreen.dropDownScreen)
        And.IRender(screen: ExamplePresentableScreen.dropDownScreen)
        When.ISetDropDown(withLabel: "textField", toValue: "Orange")
        Then.IShouldSeeTextField(textField, withText: "Orange")
    }

    ...
}

示例应用程序

创建了一个示例应用程序作为说明如何实现该框架的载体。如上面在实现部分中讨论的,实现了三个组件来针对示例应用程序编写 Swerkin 测试

测试核心类

示例测试核心类已添加到 Swerkin-UITests-Examples/TestCore 下,包括

屏幕对象类

示例应用程序中每个 ViewController 的屏幕对象示例已添加到 Swerkin-UITests-Examples/Screens 下。

测试用例

大多数 UI 元素的示例测试用例已添加到 Swerkin-UITests-Examples/Features 下。

以下是使用其可访问性标签验证名字文本字段中的文本的示例测试。

func testVerifyExistenceOfFirstNameTextFieldWithLabel() {
   Given.IAmOnScreen(ExamplePresentableScreen.textFieldScreen)
   And.IRender(screen: ExamplePresentableScreen.textFieldScreen)
   When.IWaitToSeeScreen(ExamplePresentableScreen.textFieldScreen)
   Then.IShouldSeeTextField(withLabel: "first name text Field", 
                            withText: "John")
}

如何运行示例应用程序和 UI 测试

要运行示例项目,请克隆 repo,然后首先从 Example 目录运行 pod install

选择 CMD-U Swerkin_Example scheme 来执行测试。