【问题标题】:multiple asynchronous tests and expectation多个异步测试和期望
【发布时间】:2017-10-31 05:00:57
【问题描述】:

我有多个测试,每个测试都在使用给定参数测试不同结果的相同异步方法。

我发现对于异步测试,我们必须声明一个期望,等待期望,然后实现期望。 这可以。每个测试单独完成时都能正常运行,但是当我尝试运行整个测试类时,一些测试通过,而其他测试在正常运行和通过时崩溃或失败。

我在网上到处寻找“带有期望的 swift 3 多个测试”,每个解释期望的人都只有一个测试方法的示例。 同一个类中的多个方法不可能有期望吗?

一个测试的例子如下:

func testLoginWrongUsernameOrPasswordFailure() {
  let viewModel = LoginViewModel()
  let loginAPI = APIManager()
  let expect = expectation(description: "testing for incorrect credentials")
        
  viewModel.loginWith(username: "qwerty", password: "qwerty", completion: { loginCompletion in
            
      do {
        try loginCompletion()
          XCTFail("Wrong Login didn't error")
          expect.fulfill()
        } catch let error {
          XCTAssertEqual(error as? LoginError, LoginError.wrongCredentials)
          expect.fulfill()
        }
      })
        
      waitForExpectations(timeout: 10) { error in
        XCTAssertNil(error)
      }
}

据我所知,这是对期望的正确使用,每个测试都遵循相同的模式

应 Rob 的要求,我将在此处提供 MCVE https://bitbucket.org/chirone/mcve_test 测试类使用了一个模拟 API 管理器,但是当我使用真实的 API 管理器进行测试时,仍然会出现错误。

作为对代码的解释,视图模型与给定的 API 管理器通信,该 API 管理器调用服务器并将响应返回给视图模型,以便他解释错误或成功。

第一个测试测试空字段,视图模型验证而不是 APIManager。 第二个测试测试不正确的用户名和密码 第三个测试测试有效的用户名和密码

三个测试分开运行会很好,但是当整个文件运行时我会得到一个SIGABRT错误,原因如下:

XCTAssertEqual 失败:("Optional(MCVE.LoginError.wrongCredentials)") 不等于 ("Optional(MCVE.LoginError.emptyFields)") -

*** -[XCTestExpectation 实现] 中的断言失败,/Library/Caches/com.apple.xbs/Sources/XCTest_Sim/XCTest-12124/Sources/XCTestFramework/Async/XCTestExpectation.m:101

*** 由于未捕获的异常“NSInternalInconsistencyException”而终止应用程序,原因:“API 违规 - 多次调用 -[XCTestExpectation fully] 以测试空字段。”

SIGABRT 通常发生在第二种测试方法上,如果您点击播放,那么它会在其中一种 XCTest 方法上失败,声称收到的错误不是预期的错误。

我希望 MCVE 有助于解释我的问题。

【问题讨论】:

  • 当您说“设置”时,您指的是setUp 方法还是更一般地使用该术语(即您在测试开始时调用testLoginWrongUsername,它本身,而不是来自setUp)?
  • 你能分享崩溃的细节吗?更好的是,你能分享MCVE吗?仅供参考,我想不出任何与多个异步测试固有的问题。我们这些做异步测试的人总是有很多,没有意外。如果您遇到来自多个测试的问题,我通常会怀疑单例或其他 static 变量(这就是我们试图避免它们的原因),而不是它们恰好是异步的。
  • 嗨 Rob,我已按要求添加了 MCVE。我现在意识到我对“设置”一词的使用含糊不清,并已将其从帖子中删除以进行澄清。我也认为这可能是由单例或静态变量引起的问题,但将 API 管理器设为实例也无济于事。
  • 嗨 Chirone,您的问题解决了吗?我也面临同样的问题。如果已经解决,您能否发布您的答案?
  • 我观察到,当我的第一个测试开始在测试类中运行时,它访问单例实例执行某些功能,并等待预期。与此同时,它开始在同一个类中运行我的另一个测试异步,这进一步重置了我的一个单例对象功能。

标签: swift testing asynchronous xctestexpectation


【解决方案1】:

如果您在一个 XCTestCase 中有多个测试(方法),请不要使用

let expectation = expectation(description: "")

改为使用

let expectation = XCTestExpectation(description: "")

self.expectaion() 在 XCTestCase 测试之间共享。在某些情况下,它会带来奇怪的行为。例如,即使您满足预期零次,您也可能会收到 API 违规错误。

【讨论】:

    【解决方案2】:

    重构代码如下。

    func testLoginWrongUsernameOrPasswordFailure() {
      let viewModel = LoginViewModel()
      let loginAPI = APIManager()
      let expect = expectation(description: "testing for incorrect credentials")
    
      viewModel.loginWith(username: "qwerty", password: "qwerty", completion: { loginCompletion in
    
          do {
            try loginCompletion()
            XCTFail("Wrong Login didn't error")
    
          } catch let error {
            XCTAssertEqual(error as? LoginError, LoginError.wrongCredentials)
          }
          expect.fulfill()
       })
    
      waitForExpectations(timeout: 10) { error in
        XCTAssertNil(error)
      }
    }
    

    如果您仍然遇到以下崩溃,这意味着您的异步代码完成处理程序正在调用多次。并通过多次调用 expect.fulfill() 来实现。这是不允许的。

    *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'API violation - multiple calls made to -[XCTestExpectation fulfill] for testing for empty fields.'
    

    对于期望 fulfill() 应该只调用一次。如果有一些罕见的场景并且您需要多次调用 expect.fulfill() 则设置以下属性。

    expectedFulfillmentCount
    

    请参考以下链接 https://developer.apple.com/documentation/xctest/xctestexpectation/2806572-expectedfulfillmentcount?language=objc

    【讨论】:

    • 谢谢@arango_86
    【解决方案3】:

    是否可以等待多个期望;是的。这是一个 XCTestCase 方法的签名,它显示了这一点。

    func wait(for: [XCTestExpectation], timeout: TimeInterval)
    

    有一个版本还可以确保按照它们在 for: 数组中出现的顺序来满足期望。

    在 XCode->Window->Documentation and API Reference 中查看 Apple 提供的文档,然后搜索 XCTestCase。

    【讨论】:

    • 感谢您的建议。我确实知道这种方法,但据我所知,当您在一个测试方法中实例化多个期望时,可以使用它。我有多种测试方法。对于造成的混乱,我深表歉意,并已根据 Rob 的要求调整了我的原始帖子并包含了一个小项目来说明我的问题
    • 再次检查您的 loginWith 完成处理程序。在完成处理程序的参数中传递loginCompletion 函数而不是仅仅从上下文中捕获它似乎很奇怪。 loginCompletion 中可能有一些状态会干扰后续运行。
    • 不幸的是,我不得不这样做,因为我希望 loginWith 方法抛出错误(调用者可以对错误执行它想要的操作)。但是,因为我使用的是 Alamofire,所以我不能从调用 Alamofire 请求方法的类中抛出错误,因为不兼容的返回类型 (_) throws -> ()(DataResponse<Any>) -> Void 不同(希望这是有道理的)。
    猜你喜欢
    • 2012-01-24
    • 2018-04-19
    • 1970-01-01
    • 2016-02-11
    • 2015-08-28
    • 1970-01-01
    • 2017-04-17
    • 1970-01-01
    • 2020-08-27
    相关资源
    最近更新 更多