swift 异步_Swift Swift中的单元测试:异步期望

swift 异步

In this second part of the Unit Testing in Swift series, we will cover how to properly test asynchronous code using expectations. If you are new to unit testing and want to learn how to start unit testing with Swift in Xcode, I suggest you take a step back to Part 1: The Fundamentals of the series, before reading further. This part of the series is for you, if you want to learn how to properly test the asynchronous code and functionality within your application.

Swift单元测试系列的第二部分中,我们将介绍如何使用期望值正确地测试异步代码。 如果您是单元测试的新手,并且想学习如何在Xcode中使用Swift启动单元测试,建议您进一步阅读本系列的第1部分:基础知识 。 如果您想学习如何在应用程序中正确测试异步代码和功能,本系列的该部分适合您。

让我们从一个例子开始! (Let’s begin with an example!)

Consider the School example from Part 1: The Fundamentals. Only the currently enrolled students of the school are kept in memory, but we wish to extend the School with a function for fetching all previous students (alumni) from a remote server. A function like that could look as such:

考虑第1部分:基础知识中School示例。 只有当前就读的学校学生会保留在内存中,但是我们希望扩展School ,使其具有从远程服务器获取所有以前的学生(校友)的功能。 这样的函数可能看起来像这样:

Several things can be improved in this function (including the abuse of the singleton design pattern) to allow for easier unit testing, but this will be covered in Part 3: Proper Architecture and Part 4: Mocking of this series. For now, let’s focus on writing unit tests for our function. Assuming that our Network always retrieves a list of alumni, our first test would be to check if the Result contains the correct list:

为了使单元测试更加容易,可以对该功能进行一些改进(包括滥用单例设计模式),但这将在本系列的第3部分:适当的体系结构第4部分:模拟中进行介绍。 现在,让我们专注于为我们的功能编写单元测试。 假设我们的Network总是检索校友列表,我们的第一个测试将是检查Result包含正确的列表:

In our written test, we perform the retrieveAlumni call and wait for the response. We then assert the response depending on the result. If a failure was received, we tell XCTest to fail using the XCTFail() function and if a success result was received, we assert that the number of alumni retrieved is correct. The code looks fine, but this test will ALWAYS succeed no-matter what response the network is giving us. The reason behind this is that since the completion block will be triggered on another thread at a later stage, the test function itself completes before any of the assertions have been made — and by default in Xcode, a test function succeeds if no assertions are made.

在我们的书面测试中,我们执行retrieveAlumni调用并等待响应。 然后,我们根据结果断言响应。 如果收到失败,我们使用XCTFail()函数告诉XCTest失败,并且如果收到成功结果,我们断言所检索的校友数量是正确的。 代码看起来不错,但是此测试将始终成功实现网络给我们的响应。 这背后的原因是,由于将在稍后的阶段在另一个线程上触发完成块,因此测试函数本身会在进行任何声明之前完成—并且默认情况下,在Xcode中,如果不进行声明,则测试函数会成功。

期望 (Expectations)

Luckily we have a solution for this problem: XCTestExpectation. The idea behind expectations is that we expect something to happen before completing the test. Since our test class extends XCTestCase we will be able to call the expectation(description:) function, in order to create an expectation for the test. But we also need to manually tell the test function to wait for the expectation to fulfill. This is done by using the waitForExpectations(timeout:handler:) function. Let’s see how this works:

幸运的是,我们有一个解决此问题的方法: XCTestExpectation 。 期望值背后的想法是,我们期望在完成测试之前会发生一些事情。 由于我们的测试类扩展了XCTestCase我们将能够调用XCTestCase expectation(description:)函数,以便为测试创建期望。 但是我们还需要手动告诉测试函数等待期望的实现。 这是通过使用waitForExpectations(timeout:handler:)函数完成的。 让我们看看它是如何工作的:

Notice how we manually fulfill() the expectation — this gives us more control of the asynchronous nature of the test. In the waitForExpectations call, we additionally provide a timeout in seconds. This tells the test that it should wait for the expectations to fulfill within the given number of seconds and the test won’t complete until the expectation has either been fulfilled or the timeout has been reached.

注意我们如何手动fulfill()期望fulfill()期望)—这使我们可以更好地控制测试的异步性质。 在waitForExpectations调用中,我们还提供了以秒为单位的超时。 这告诉测试,它应该等待期望值在给定的秒数内完成,并且直到达到期望值或达到超时,测试才会完成。

期望倒转 (Inverted Expectations)

But what if we don’t expect something to happen? This can be tested as well with inverted expectations. To invert an expectation we simply just set the isInverted flag to true. A few years back, before the introduction of Result, we commonly saw completion handlers for both successful and failing events within a function like retrieveAlumni. If that was the case, we could test that the failure block would never be triggered, with an inverted expectation like so:

但是,如果我们不期望发生什么事情怎么办? 反向期望也可以测试这一点。 要反转期望,我们只需将isInverted标志设置为true 。 几年前,在引入Result之前,我们通常会在诸如retrieveAlumni之类的函数中看到成功和失败事件的完成处理程序。 如果真是这样,我们可以测试失败块是否永远不会被触发,其期望值是这样的:

Note that for the inverted test, we still manually fulfill the expectation, but because it is inverted, we do not expect it to be fulfilled. This also means that the waitForExpectations call will meet its timeout and when it does, the test completes. For the same reason, testing with inverted expectations may significantly slow down your unit test suite, as each test with an inverted expectation has to wait for the timeout to complete.

请注意,对于反向测试,我们仍然手动满足期望,但是由于它是反向的,因此我们不希望它得到满足。 这也意味着, waitForExpectations调用将满足其超时要求,并且当超时时,测试将完成。 出于同样的原因,期望值倒置的测试可能会大大降低您的单元测试套件的速度,因为每个期望值倒置的测试都必须等待超时完成。

测试异步FAF功能 (Testing Asynchronous FAF Functions)

The use of expectations like above, requires the tested function to trigger a completion block when the asynchronous task has completed. But not all functions have completion blocks — some just perform an asynchronous task in the background. These functions are typically referred to as “fire and forget” (FAF) functions. There are two common options of testing FAF functions. The option to choose depends on where the asynchronous queue is being created in your code.

像上面那样使用期望值,要求被测试的函数在异步任务完成时触发完成块。 但是,并非所有功能都具有完成功能块-有些功能只是在后台执行异步任务。 这些功能通常称为“ 火灾后遗忘 ”(FAF)功能。 有两种常见的测试FAF功能的选项。 选择的选项取决于在代码中创建异步队列的位置。

在要测试的函数中创建的队列 (Queues created in the function being tested)

If the asynchronous queue is being created within the function your are testing, your only option is to use injection for the function. By injecting the queue instead of creating it within the function, we allow for full control of the thread and hence we are able to synchronise it before making our assertions in the test. Consider for instance the following function:

如果要在要测试的函数中创建异步队列,则唯一的选择是对函数使用注入。 通过注入队列而不是在函数中创建队列,我们​​可以完全控制线程,因此可以在测试中声明之前将其同步。 例如考虑以下功能:

All it does is to create an asynchronous queue that waits for 1 second before setting the didComplete property to true. But if we attempt to test it we will face the same issue as we did in the beginning of this article. By injecting the queue we can, however, control the test scenario…

它所做的就是创建一个异步队列,该队列等待1秒,然后将didComplete属性设置为true 。 但是,如果尝试对其进行测试,我们将面临与本文开头相同的问题。 通过注入队列,我们​​可以控制测试方案……

… by synchronising the queue within our test:

…通过同步测试中的队列:

As easy as that we are able to test our FAF function.But what about the cases when the function itself does not create the queue?

我们可以轻松测试FAF函数,但是如果函数本身不创建队列怎么办?

由FAF函数使用的依赖项创建的队列 (Queues created by dependencies used by the FAF function)

When the asynchronous queue is created by a dependency used within the function, the solution is to remove the asynchronous nature of the function itself. By “mocking” the dependency we will be in complete control of the test and hence remove the asynchronous code without modifying the tested function itself. I am a strong advocate for the use of mocked dependencies in all unit tests, as being in complete control of the test environment is key. For the same reason Part 4: Mocking of this series is dedicated specifically to implementing mock classes to make your life much easier when writing unit tests.

当异步队列由函数内使用的依赖关系创建时,解决方案是删除函数本身的异步特性。 通过“模拟”依赖项,我们将完全控制测试,因此无需修改测试功能本身即可删除异步代码。 我强烈建议在所有单元测试中使用模拟依赖项,因为完全控制测试环境是关键。 出于同样的原因,本系列的第4部分:模拟专门致力于实现模拟类,从而使编写单元测试时的工作变得更加轻松。

下一步…更好的整体架构! (Next steps… Better overall architecture!)

As you may also have discovered by now, dependency injection is a pattern that allows for easier unit testing with high quality tests. Part 3: Proper Architecture of this series will go further in depth with general architectural patterns that are useful (and not so useful) to implement throughout your project, in order to enhance the overall quality of your test suite.

正如您现在可能已经发现的那样,依赖项注入是一种模式,可以通过高质量测试简化单元测试。 第3部分:本系列的适当体系结构将以通用的体系结构模式进一步深入,这些模式对于整个项目都是有用的(但不是那么有用),以提高测试套件的整体质量。

As always, if you have any questions or comments, feel free to reach out to me by commenting on these articles. I will reply to all messages.

与往常一样,如果您有任何问题或意见,请随时评论这些文章与我联系。 我将回复所有消息。

翻译自: https://medium.com/swlh/unit-testing-in-swift-asynchronous-expectations-376e1427aeb9

swift 异步

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值