XCUITest的机器人模式测试

在iOS上使用机器人图案

我最近在iOSDevUK上发表演讲,概述了我们如何在Capital One测试我们的旗舰iOS应用程序。 我被问到的最常见的跟进问题是关于使用机器人图案 我在幻灯片中简要介绍了机器人模式,但显然这是其他人有兴趣了解的更多信息

机器人图案是由Square的Jake Wharton设计的,用于在Kotlin进行测试。 结果,许多可用信息都集中在Kotlin和Espresso测试上。 在线使用iOS的机器人模式的知识很少。 但是,Capital One UK Mobile团队选择使用机器人图案,因为这意味着我们的Android和iOS测试之间采用一致的方法。 这是iOS应用程式的运作方式。

为什么要使用机器人图案

编写XCUI测试时,使用机器人模式的三个主要原因

  1. 易于理解
    我们来自Calabash的XCUITests,我们的测试是用Cucumber编写的。 黄瓜接近自然语言。 这意味着您可以快速,轻松地阅读和理解所测试的内容,而无需确切了解测试的工作方式。 用本机代码编写测试,知道要测试的内容和没有测试的内容已经有了一条学习曲线。
  2. 重用代码
    通过将我们的测试分为多个步骤,每个实现步骤都可以根据需要重复使用多次。 如果您的应用在执行任何操作之前具有登录屏幕,则每个测试的设置很多。 取而代之的是,您只需每次调用​​login(),然后继续进行测试的更具体区域。 如果确实需要做一些不同的事情,可以将参数传递给函数。
  3. 隔离实施细节
    无论您的应用程序使用哪种架构,您的目标都是单一责任原则。 坚持这样做可以使您以新的实现将对象切换为新对象,同时仍保持对象的核心功能。 这使得代码更易于维护,测试和改进。 那么,为什么您的测试应该有所不同? 杰克·沃顿(Jake Wharton)将其描述为将“内容”与“方法”分开。 您的测试仅应关注“什么”,这意味着,如果您的视图改变事物在屏幕上的显示或发生方式,则无需更改整个测试套件。

编写一个XCUITest

假设我们正在为Apple的内置Messages应用程序编写UI测试。 如果您从暂停状态加载应用程序,则会在屏幕顶部看到一个大标题“消息”和一个用于创建新消息的按钮。 假设我们正在测试点击此按钮,然后使用iMessage向用户发送新消息。

我们的XCUITest可能看起来像这样:

  func test_sendNewiMessage(){ 
让app = XCUIApplication()
app.launch()

app.buttons [“ new_message”]。tap()

让newMessage = app.staticTexts [“新消息”]
let谓词= NSPredicate(格式:“存在== true”)
让期望= XCTNSPredicateExpectation(谓词:谓词,对象:newMessage)
让结果= XCTWaiter.wait(用于:[期望],超时:5)
XCTAssertEqual(结果,.completed)

app.typeText(“ iMessage联系人”)

让newimessage = app.staticTexts [“新iMessage”]
让newimessagePredicate = NSPredicate(格式:“存在== true”)
让newiMessageExpectation = XCTNSPredicateExpectation(谓词:newimessagePredicate,对象:newimessage)
让newiMessageResult = XCTWaiter.wait(用于:[newiMessageExpectation],超时:5)
XCTAssertEqual(newiMessageResult,.completed)

让firstField = app.textFields [“ messageField”]

firstField.typeText(“测试iMessage”)
app.buttons [“发送”] .tap()

让消息= app.staticTexts [“测试iMessage”]
让messagePredicate = NSPredicate(格式:“存在== true”)
让messageExpectation = XCTNSPredicateExpectation(predicate:messagePredicate,object:message)
让messageResult = XCTWaiter.wait(用于:[messageExpectation],超时:5)
XCTAssertEqual(messageResult,.completed)
}

显然,此测试存在多个问题-有很多重复的代码,很难跟踪正在测试的内容。 但是,如果我们还想测试向SMS联系人发送新消息该怎么办? 我们必须重复整个测试。 然后,如果我们对UI进行了真正的更改,则必须更改两个测试。

创建一个机器人

在此测试期间,我们将访问两个屏幕。

  • 对话的初始列表,其标题为“消息”。
  • 点击新的消息按钮后,将显示对话详细信息屏幕。

我们将创建一个基本的Robot类,其中包含一些常用功能,例如断言元素是否存在以及在屏幕上点击。 然后,每个屏幕都有其自己的扩展Robot的Robot类。 这些特定于屏幕的漫游器包含特定于该屏幕的动作,因此我们的对话列表将包含一个高级功能来创建新消息。 在此测试中,我们的对话详细信息还有更多内容,因为这是我们将花费大部分时间的地方。

 导入XCTest 
机器人类{ 
var app = XCUIApplication()

func tap(_元素:XCUIElement,超时:TimeInterval = 5){
让期望= XCTNSPredicateExpectation(谓词:NSPredicate(格式:“ isHittable == true”),对象:元素)
警卫XCTWaiter.wait(用于:[期望],超时:超时)==。已完成否则{
XCTAssert(false,“无法命中元素\(element.label)”)
}
}

func assertExists(_元素:XCUIElement…,超时:TimeInterval = 5){
让期望= XCTNSPredicateExpectation(谓词:NSPredicate(格式:“存在== true”),对象:元素)
警卫XCTWaiter.wait(用于:[期望],超时:超时)==。已完成否则{
XCTAssert(false,“元素不存在”)
}
}
}
 类ConversationListRobot:机器人{ 

惰性私有变量newConversationButton = app.buttons [“ new_message”]

func newConversation()-> ConversationDetailRobot {
点击(newConversationButton)
}
}
  class ConversationDetailRobot:机器人{ 

private var messageType =“消息”
懒惰的专用var screenTitle = app.staticTexts [“ New \(messageType)”]
惰性私有变量contactField = app.textFields [“ contact”]
惰性私有变量取消= app.buttons [“取消”]
惰性私有变量messageField = app.textFields [“ messageField”]
惰性私有变量sendButton = app.buttons [“发送”]

func checkScreen(messageType:String)->自我{
self.messageType = messageType
assertExists(screenTitle,contactField,cancel,message field,sendButton)
返回自我
}

func enterContact(contact:String)-> Self {
点击(contactField)
contactField.typeText(contact)
返回自我
}

func enterMessage(消息:字符串)->自我{
点击(messageField)
messageField.typeText(message)
返回自我
}

func sendMessage()->自我{
点击(发送按钮)
返回自我
}

@discardableResult
func checkConversationContains(消息:字符串)->自我{
让messageBubble = app.staticTexts [message]
assertExists(messageBubble)
返回自我
}
}

然后,这些允许我们将功能链接在一起以创建测试,因此上面的示例变为:

  func test_sendNewiMessage(){ 

let message =“测试消息”

XCUIApplication()。launch()

ConversationListRobot()
.newConversation()
.checkConversationContains(消息:“消息”)
.enterContact(联系人:“ iMessage联系人”)
.checkScreen(messageType:“ iMessage”)
.enterMessage(消息:消息)
。发信息()
.checkConversationContains(消息:消息)
}

立即,这使一目了然的阅读和理解变得更加容易,而无需知道应用程序如何运行或如何编写Swift。

建筑模块

然后可以将这些高级功能以不同的配置链接在一起,具体取决于我们要测试的内容。 假设我们要尝试相同的操作,但是要通过短信联系。 我们将进行新的测试,将联系人更改为“ SMS联系人”,在第二个checkScreen()上,我们的消息类型将为“消息”。

使用此技术,我们可以轻松地从我们创建的构建块构建几个测试:

  • 无效的联系人。
  • 返回消息列表。
  • 尝试发送空白消息。
  • 发送媒体

所有这些函数都使用了这些基本函数,并传递了不同的值,或者以不同的顺序传递了值,或者可能在需要时添加了新值。 而且,如果用于发送消息的按钮发生了变化,我们只需要在sendMessage()的实现中进行更改,而不必在每个测试中进行更改。

结论

我强烈建议您熟悉XCUITesting,这是一种非常简单的方法,可以测试您的应用程序向用户呈现的内容是否符合您的期望。 据我所知,它在犯罪方面没有得到充分利用。 实践证明,机器人图案是一种干净,简单的技术,已为我们的团队解决了许多问题。 真的值得考虑是否会为您做同样的事情。

尽管很少有关于在Swift和iOS上使用Robot Pattern的文档,但FarukToptaş在撰写文章时提供了一些示例Android Kotlin代码,而Robot Pattern的设计师Jake Wharton就其设计背后的决策进行了精彩的介绍性演讲。