翻译:您在Swift 5中的第一个UITest

翻译:您在Swift 5中的第一个UITest

1. 什么是自动UITest?

当谈到测试时,我们通常会考虑单元测试。但是,在应用程序世界中还有另一种功能极其强大且有用的测试:UITests。
这些测试的目的是验证您的UI行为是否符合预期:按钮处于正确的状态,字符串的样式正确,导航在我们进行编码时发生。
测试的主体模拟了与UI交互的use r,从而导致应用程序随着时间的推移而发展,并允许我们断言某些条件,我们希望在应用程序执行过程中满足要求。

2. 为什么要执行自动UITest?

作为所有测试活动,编写适当的UITests需要时间。但是,这段时间将来将为我们节省很多麻烦,从长远来看,还将节省比其所消耗的时间更多的时间。

编写自动化的UITests有很多好处,特别是在应用变得非常大且可能难以维护的情况下。其中一些好处:

  • 无需测试旧功能。在敏捷的环境中,我们每两周创建一次新功能。但是,开发人员有些偏执:他们会要求QA朋友每次都测试整个应用程序,以避免性能下降。可以使用自动化的UITest自动浏览最常见的用例,从而保持开发人员和质量检查人员的头脑清醒。
  • 编码用例知识。UITests允许我们查看事件序列发生时应用程序的行为。这对于跟踪常见用例并验证每次我们修改应用程序时它们不会崩溃都非常有用。
  • 测试UI性能。假设您的应用必须执行一些繁重的UI操作,例如滚动浏览一大堆图像,我们可以使用UITests来衡量执行这些操作所需的时间以及是否存在回归。
  • 检查本地化。假设您的应用程序已本地化,我们可以自动检查是否用省略号剪切了某些字符串。

这些只是自动UITest的一些优点。可能会有更多,但我认为这些是最突出的。当然也有缺点:

  • 他们比单元测试花费更多的时间。
  • 鉴于UI可以经常更改,因此它们往往更加不稳定。您的UITests有可能在6个月内过时。

尽管存在这些缺点,但我认为这些测试确实有帮助,特别是对于某些部分不太可能更改的大型应用程序。如果您必须由一个只能同时关注一个应用程序的小型团队管理多个应用程序,它们也很有用。

3. 如何编写UITests?

搭建环境
UITests已集成到Xcode中,我们的IDE为我们完成了大部分工作。第一步是为我们的应用添加一个新的UITest目标。

  1. 点击 File > New > Target
  2. 选择 UI Testing Bundle
  3. 单击Next并在表单中填写名称
  4. 点击Finish。

Xcode将使用第一个空UITest文件创建新目标。该文件具有以下结构(请注意Xcode提供了多少注释,以帮助我们进行首次尝试)。

import XCTest

class UITestAppUITests: XCTestCase {

    override func setUpWithError() throws {
        // Put setup code here. This method is called before the invocation of each test method in the class.
        // In UI tests it is usually best to stop immediately when a failure occurs.
        continueAfterFailure = false

        // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
    }

    override func tearDownWithError() throws {
        // Put teardown code here. This method is called after the invocation of each test method in the class.
    }

    func testExample() throws {
        // UI tests must launch the application that they test.
        let app = XCUIApplication()
        app.launch()

        // Use recording to get started writing UI tests.
        // Use XCTAssert and related functions to verify your tests produce the correct results.
    }

    func testLaunchPerformance() throws {
        if #available(macOS 10.15, iOS 13.0, tvOS 13.0, *) {
            // This measures how long it takes to launch your application.
            measure(metrics: [XCTOSSignpostMetric.applicationLaunch]) {
                XCUIApplication().launch()
            }
        }
    }
}

让我们专注于该testExample()方法。一切都由一个XCUIApplication()对象管理。该对象是我们应用程序的抽象。我们可以launch()并且terminate()它可以让我们查询UI中的所有元素。

4. 记录我们的第一个测试

是的,你没看错!我们不会立即编写测试:Xcode提供了一个非常不错且有用的工具,允许我们与应用程序进行交互,而Xcode会将我们的交互转换为代码。多么酷啊?!
要开始此操作,我们只需要将光标置于第testExample()24行下方的empty方法中,然后按一下record(红色按钮是record按钮。), Xcode底部的小按钮即可。

www.zeeklog.com  - 翻译:您在Swift 5中的第一个UITest


当record按下按钮时,Xcode将启动我们的应用程序并捕获所有交互,为我们编写代码。
对于本文,我准备了一个非常简单的应用程序(),该应用程序view具有3个按钮:一个privacy按钮,一个tos(服务条款)按钮和一个继续按钮。Continue除非用户同时点击privacytos按钮,否则该按钮将被禁用。一旦两个按钮都被选中,该Continue按钮将变为启用状态,并且用户HomeView在点击该Continue按钮时会导航到蓝色。

www.zeeklog.com  - 翻译:您在Swift 5中的第一个UITest


屏幕A。未选择按钮,继续禁用;屏幕B。选择了服务条款,继续禁用;屏幕C,两个按钮均被选中,继续启用;屏幕D,蓝色HomeView。

让我们做一个快速实验并开始记录测试。在此用例中,我们要:

  1. 轻按 Tap to accept TOS
  2. 轻按 Tap to Accept Privacy Policy
  3. 确认已Continue启用
  4. 点按Continue,即可看到该应用导航至蓝色view
  5. 停止录音。
    此过程的输出是以下代码:
func testExample() throws {
  // UI tests must launch the application that they test.
  let app = XCUIApplication()
  app.launch()

  // Use recording to get started writing UI tests.
  // Use XCTAssert and related functions to verify your tests produce the correct results.
  // XCODE STARTS WRITING HERE:👇
  let app = XCUIApplication()
  app.staticTexts["Tap to accept TOS"].tap()
  app.staticTexts["Tap to Accept Privacy Policy"].tap()
  app.staticTexts["Continue"].tap()

}

注意,Xcode再次写入letapp = XCUIApplication()行。我们必须将其删除。

现在通过按来运行测试cmd+U,会导致Xcode为我们运行该应用程序并再次执行我们刚执行的操作。测试结果是成功的,主要是因为我们没有编写任何断言。

5. 写断言

每当我们必须测试一段代码时,我们就必须编写断言。断言通常是布尔检查,可让我们声明一些条件,这些条件必须有效才能使测试成功。

通过利用XCTest框架,在UITests中编写断言与在标准单元测试中编写断言完全相同。

让我们将断言添加到我们的代码中。

let tosButton = app.staticTexts["Tap to accept TOS"]
let privacyButton = app.staticTexts["Tap to Accept Privacy Policy"]
let continueButton = app.staticTexts["Continue"]

XCTAssertFalse(tosButton.isSelected)
XCTAssertFalse(privacyButton.isSelected)
XCTAssertFalse(continueButton.isEnabled)

tosButton.tap()

XCTAssertTrue(tosButton.isSelected)
XCTAssertFalse(privacyButton.isSelected)
XCTAssertFalse(continueButton.isEnabled)

privacyButton.tap()

XCTAssertTrue(tosButton.isSelected)
XCTAssertTrue(privacyButton.isSelected)
XCTAssertTrue(continueButton.isEnabled)

continueButton.tap()

首先,我们将三个按钮分解为单独的变量。这样,我们可以查询它们的状态。然后我们写断言。它们非常简单:只需检查按钮的状态即可,因为我们知道它在执行期间会发生变化。

运行测试,看看…他们失败了!😨

那怎么可能 好吧,让我们分析错误和代码。

第一个问题是continueButton.isEnabled自测试开始以来该属性返回true这是因为我们将其作为staticText而不是作为进行访问button。静态文本没有启用或禁用的概念,因此该isEnabled属性始终返回true。首先执行更改:将所有staticTexts更改为buttons

让我们再次运行测试,……另一个失败!第二组断言不再找到tosButton!不用担心,这里有一个合理的解释。当我们点击按钮时,按钮的标题会变化,tosButton怎样?我们用来访问button字典的内容是什么?在tosButton.title当按钮被窃听,我们正在使用的原标题查询按钮词典性质的变化。

幸运的是,我们可以使用来克服此问题accessibilityIdentifier。无障碍技术使iPhone可以向用户描述屏幕上的内容。UITests利用相同的技术来索引视图中的元素。因此,我们只需要向类中的setup()方法添加第7至10行即可LegalView

private func setup() {
  // ... other view elements ...
  self.addSubview(tosButton)
  self.addSubview(privacyButton)
  self.addSubview(continueButton)
   
  // Add these lines to let the XCUITest access the buttons with identifiers
  self.tosButton.accessibilityIdentifier = "legal_view.tos_button"
  self.privacyButton.accessibilityIdentifier = "legal_view.privacy_button"
  self.continueButton.accessibilityIdentifier = "legal_view.continue_button"
  
  // ...
}

现在,我们已经id为每个按钮分配了一个,我们可以使用它来检索适当的UI元素。

您可能要问的最后一个问题是,我们如何确定应用正确导航到正确的视图?好的,通过结合accessibilityIdentifiersXCUIApplication.otherElements属性,我们可以编写一个简单的断言来验证一切是否如我们所愿。实际上,我们可以:

  1. "home_view”可访问性标识符添加到蓝色视图。
  2. 导航后,检查是否存在带有该标识符的元素。
    现在,我们可以按cmd+U运行代码,并查看每个断言都通过了。具有正确断言的最终代码如下所示:
func testExample() throws {
    // UI tests must launch the application that they test.
    let app = XCUIApplication()
    app.launch()

    // Use recording to get started writing UI tests.
    // Use XCTAssert and related functions to verify your tests produce the correct results.
  let tosButton = app.buttons["legal_view.tos_button"]
  let privacyButton = app.buttons["legal_view.privacy_button"]
  let continueButton = app.buttons["legal_view.continue_button"]

  XCTAssertFalse(tosButton.isSelected)
  XCTAssertFalse(privacyButton.isSelected)
  XCTAssertFalse(continueButton.isEnabled)

  tosButton.tap()

  XCTAssertTrue(tosButton.isSelected)
  XCTAssertFalse(privacyButton.isSelected)
  XCTAssertFalse(continueButton.isEnabled)

  privacyButton.tap()

  XCTAssertTrue(tosButton.isSelected)
  XCTAssertTrue(privacyButton.isSelected)
  XCTAssertTrue(continueButton.isEnabled)

  continueButton.tap()

  XCTAssertTrue(app.otherElements["home_view"].exists)
}

我还要添加许多其他内容,但下周将保留它们。本文已经充满了我认为我们应该休息一下的概念和想法。

您将在网上找到的代码稍微先进一些:请记住,测试与您的业务代码同等重要,因此您需要加以注意。这就是为什么我准备的代码井井有条,并排除了检查,并对状态更改逻辑和导航进行了两种不同的测试。

参考

https://uxdesign.cc/your-first-uitest-in-swift-847bc5595c26

Read more

深入理解 Proxy 和 Object.defineProperty

在JavaScript中,对象是一种核心的数据结构,而对对象的操作也是开发中经常遇到的任务。在这个过程中,我们经常会使用到两个重要的特性:Proxy和Object.defineProperty。这两者都允许我们在对象上进行拦截和自定义操作,但它们在实现方式、应用场景和灵活性等方面存在一些显著的区别。本文将深入比较Proxy和Object.defineProperty,包括它们的基本概念、使用示例以及适用场景,以帮助读者更好地理解和运用这两个特性。 1. Object.defineProperty 1.1 基本概念 Object.defineProperty 是 ECMAScript 5 引入的一个方法,用于直接在对象上定义新属性或修改已有属性。它的基本语法如下: javascript 代码解读复制代码Object.defineProperty(obj, prop, descriptor); 其中,obj是目标对象,prop是要定义或修改的属性名,descriptor是一个描述符对象,用于定义属性的特性。 1.2 使用示例 javascript 代码解读复制代码//

By Ne0inhk

Proxy 和 Object.defineProperty 的区别

Proxy 和 Object.defineProperty 是 JavaScript 中两个不同的特性,它们的作用也不完全相同。 Object.defineProperty 允许你在一个对象上定义一个新属性或者修改一个已有属性。通过这个方法你可以精确地定义属性的特征,比如它是否可写、可枚举、可配置等。该方法的使用场景通常是需要在一个对象上创建一个属性,然后控制这个属性的行为。 Proxy 也可以用来代理一个对象,但是相比于 Object.defineProperty,它提供了更加强大的功能。使用 Proxy 可以截获并重定义对象的基本操作,比如访问属性、赋值、函数调用等等。在这些操作被执行之前,可以通过拦截器函数对这些操作进行拦截和修改。因此,通过 Proxy,你可以完全重写一个对象的默认行为。该方法的使用场景通常是需要对一个对象的行为进行定制化,或者需要在对象上添加额外的功能。 对比 以下是 Proxy 和 Object.defineProperty 的一些区别对比: 方面ProxyObject.defineProperty语法使用 new Proxy(target,

By Ne0inhk