Swift TDD &异步 URLSession - 如何测试? [英] Swift TDD & async URLSession - how to test?

查看:27
本文介绍了Swift TDD &异步 URLSession - 如何测试?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我尝试熟悉 TDD.如何测试异步的 URLSession 调用?哪个 XCAssert 更适合使用,在哪里,在哪个阶段?

我的第一个想法是创建一个函数,其中包含 URLSession,在该函数中将 bool 标志设置为 true,然后在 XCAssertTrue 中对其进行测试.或者另一个想法是在调用包含USLSession代码的函数后同步返回假数据.

解决方案

好问题.我觉得可以分解成两部分,第一部分是关于如何使用XCTest测试异步代码,第二部分是关于您建议的策略.

异步代码的问题在于它与测试运行在不同的线程上,这导致测试在异步调用之后继续进行,并在异步调用完成之前完成.

XCTest 框架提供了一个 waitForExpectationsWithTimeout:handler 专门为此用例构建的函数.它允许您等待异步调用完成,然后才检查其结果.

您可以这样使用它:

import XCTest@testable 导入 MyApp类回调测试:XCTestCase {func testAsyncCalback() {让服务 = SomeService()//1. 定义一个期望let expect = ExpectationWithDescription("SomeService 执行操作并运行回调闭包")//2. 练习异步代码service.doSomethingAsync { 成功XCTAssertTrue(成功)//不要忘记在异步回调中满足期望期望.实现()}//3. 等待期望被满足waitForExpectationsWithTimeout(1) { 错误如果让错误 = 错误 {XCTFail("waitForExpectationsWithTimeout 错误:\(error)")}}}}

您可以在此博客中阅读有关该方法的更多信息发布.完全公开,我写的.

现在,关于您的建议:

<块引用>

我的第一个想法是创建一个函数,其中包含 URLSession,在该函数中将 bool 标志设置为 true,然后在 XCAssertTrue 中对其进行测试.

好主意.这是测试是否实际调用异步回调的好方法.唯一要记住的是,测试需要等待异步调用运行,否则总是会失败.

使用上面的技巧你可以写:

let service = SomeService()var 调用 = false让期望=expectationWithDescription(实际上调用了传递给 SomeService 的回调")service.doSomethingAsync { _ in被称为 = 真期望.实现()}waitForExpectationsWithTimeout(1) { 错误如果让错误 = 错误 {XCTFail("waitForExpectationsWithTimeout 错误:\(error)")}XCTAssertTrue(调用)}

<块引用>

或者另一个想法是在调用包含URLSession代码的函数后同步返回假数据.

这是另一个好主意.将软件分解为只做一件事的组件,单独对它们进行彻底的测试,最后通过一些强调快乐和最常见故障路径的集成测试来测试它们是否能一起工作.

既然您提到了 URLSession,我想就涉及网络的单元测试代码留下警告说明.在单元测试中使用真实的网络调用通常是一个坏主意.事实上,它将测试绑定到网络可用并返回预期结果的事实,同时也使测试变慢.

在单元级别,我们希望我们的测试是隔离的、确定性的和快速的(更多关于这个 此处).打入网络与这些目标不符.

避免访问网络的一个好方法是对其进行存根,方法是将 URLSession 包装到协议中,然后在测试中使用符合它的伪造,或者使用像 OHHTTPStubs.这篇文章更多细节,再次完全公开,我也写了这个.

希望这会有所帮助.单元测试异步代码并非微不足道,您应该耐心等待,如果某些事情看起来不对,请继续提问:)

I try to get familiar with TDD. How can I test URLSession calls which are asynchronous ? Which XCAssert better to use and where, in which stage ?

My first thought was to create a function, that has URLSession inside it, and inside this function set bool flag to true and then test it in XCAssertTrue. Or another thought was to return fake data synchronously after calling function which contains USLSession code.

解决方案

Nice question. I think it can be decomposed in two parts, the first is about how to test asynchronous code using XCTest, the second is about the strategies that you suggest.

The problem with asynchronous code is that it runs on a different thread as the test, which results in the test moving on after the async call, and finishing before that has completed.

The XCTest framework provides a waitForExpectationsWithTimeout:handler function that is built just for this use case. It allows you to wait for the async call to have finished and only then check its result.

This is how you could use it:

import XCTest
@testable import MyApp

class CallbackTest: XCTestCase {

  func testAsyncCalback() {
    let service = SomeService()

    // 1. Define an expectation
    let expectation = expectationWithDescription("SomeService does stuff and runs the callback closure")

    // 2. Exercise the asynchronous code
    service.doSomethingAsync { success in
      XCTAssertTrue(success)

      // Don't forget to fulfill the expectation in the async callback
      expectation.fulfill()
    }

    // 3. Wait for the expectation to be fulfilled
    waitForExpectationsWithTimeout(1) { error in
      if let error = error {
        XCTFail("waitForExpectationsWithTimeout errored: \(error)")
      }
    }
  }
}

You can read more about the approach in this blog post. Full disclosure, I wrote it.

Now, regarding your suggestions:

My first thought was to create a function, that has URLSession inside it, and inside this function set bool flag to true and then test it in XCAssertTrue.

Good idea. That's a great way to test if an asynchronous callback is actually called. The only thing to keep in mind is that the test needs to wait for the async call to run, or it will always fail.

Using the technique above you could write:

let service = SomeService()
var called = false

let expectation = expectationWithDescription(
    "The callback passed to SomeService is actually called"
)

service.doSomethingAsync { _ in
  called = true
  expectation.fulfill()
}

waitForExpectationsWithTimeout(1) { error in
  if let error = error {
    XCTFail("waitForExpectationsWithTimeout errored: \(error)")
  }

  XCTAssertTrue(called)
}

Or another thought was to return fake data synchronously after calling function which contains URLSession code.

That's another great idea. Decomposing software in components that do only one thing, test them all thoroughly in isolation, and finally test that they all work together with a few integration tests that stress the happy and most common failure paths.

Since you are mentioning URLSession I'd like to leave a warning note regarding unit testing code that touches the network. Having real network calls in your unit tests is generally a bad idea. In fact, it binds the tests to the fact that the network will be available and return the expected result, and also makes them slower.

At a unit level we want our test to be isolated, deterministic, and fast (more on this here). Hitting the network doesn't align with these goals.

A good approach to avoid hitting the network is to stub it, either by wrapping URLSession into a protocol and then using a fake conforming to it in your tests, or by using a library like OHHTTPStubs. This post goes more into details, again full disclosure, I wrote this one too.

Hope this helps. Unit testing asynchronous code is not trivial, you should be patient with it and keep asking question if something doesn't seem right :)

这篇关于Swift TDD &amp;异步 URLSession - 如何测试?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆