读者好!
测试是任何开发必不可少的部分,这已经不是什么秘密了。 在之前的文章中,我们介绍了Clean Swift架构的基本架构,现在是时候学习如何使用测试覆盖其Unit了 。 我们将以关于工人的文章中的项目为基础,并分析要点。

理论
由于依赖注入和面向协议 , Clean Swift中的所有场景组件都彼此独立,可以单独进行测试。 例如, Interactor依赖于Presenter和Worker ,但是这些依赖关系是可选的并且基于协议。 因此, Interactor可以在没有Presenter'a和Worker'a的情况下执行其工作(尽管很差),并且我们还可以将其替换为根据其协议签名的其他对象。
由于我们要分别测试每个组件,因此需要用伪组件替换依赖项 。 这将帮助我们进行间谍(Spy)。 间谍是测试对象,它们实现了我们想要注入并跟踪其中的方法调用的协议。 换句话说,我们为Presenter和Worker创建Spy ,然后将它们注入到Interactor中以跟踪方法调用。

公平地说,我还要添加一些测试对象( 测试双打 ) Dummy , Fake , Stub和Mock 。 但是在本文的框架内,我们不会影响它们。 在此处阅读更多内容-TestDoubles
练习
最后,让我们开始做生意。 为了节省您的时间,我们将考虑抽象代码,而不涉及每种方法的实现细节。 可以在此处找到应用程序代码的详细信息 : CleanSwiftTests
对于场景的每个组件,我们创建一个包含测试的文件,并测试组件依赖关系的测试倍数 ( Test Doubles )。
这种结构的一个例子:

每个测试文件(用于组件)的结构看起来都相同,并且遵循以下编写顺序:
- 我们用SUT (我们要测试的对象)声明一个变量,并声明其主要依赖关系的变量
- 我们在setUp()中初始化SUT及其依赖项,然后在tearDown()中清除它们
- 测试方法
正如我们在理论上所讨论的,可以分别测试场景的每个组成部分。 我们可以将测试副本( Spy )注入其依赖项,从而监视SUT方法的操作。 让我们仔细研究一下使用Home场景的Interactor示例编写测试的过程。
HomeInteractor依赖于两个对象-Presenter和Worker 。 该类中的两个变量都具有协议类型。 这意味着我们可以创建根据HomePresentationLogic和HomeWorkingLogic协议签名的测试副本,然后将其注入HomeInteractor中 。
final class HomeInteractor: HomeBusinessLogic, HomeDataStore {
我们将测试两种方法:
- fetchUsers(:) 。 负责通过API获取用户列表。 使用Worker发送API请求。
- selectUser(:) 。 负责从已加载的用户( users )列表中选择活动用户( selectedUser )。
要开始编写Interactor的测试,我们需要创建间谍程序,以跟踪HomePresentationLogic和HomeWorkingLogic中方法的调用。 为此,请在目录“ CleanSwiftTestsTests / Stores / Home / TestDoubles / Spies”中创建HomePresentationLogicSpy类,签署协议HomePresentationLogic并实现此协议的方法。
final class HomePresentationLogicSpy: HomePresentationLogic {
这里的一切都非常透明。 如果调用presentFetchedUsers ( HomePresentationLogic )方法,则将isCalledPresentFetchedUsers变量的值设置为true 。 因此,我们可以跟踪在Interactor的测试过程中是否调用了此方法。
使用相同的原理,创建HomeWorkingLogicSpy 。 区别之一是我们称为完成 ,因为 Interactor中的部分代码将被包装在此方法的闭包中。 HomeWorkingLogic方法处理网络请求。 在测试期间,我们需要避免实际的网络请求。 为此,我们将其替换为测试用例,该用例跟踪方法调用并返回模板数据,但不向网络发出任何请求。
final class HomeWorkingLogicSpy: HomeWorkingLogic {
接下来,我们创建HomeInteractorTests类,通过它我们将测试HomeInteractor 。
final class HomeInteractorTests: XCTestCase {
我们指出了三个主要变量-sut , worker和presenter 。
在setUp()中,初始化必要的对象,在Interactor中注入依赖项,并将对象分配给类变量。
在tearDown()中,我们清除类变量以确保实验的纯度。
在该方法完成工作之前,将在测试方法开始之前调用setUp()方法,例如testFetchUsers()和tearDown() 。 因此,我们在每个测试方法运行之前重新创建测试对象( sut )。
接下来是测试方法本身。 该结构分为3个主要逻辑块-必要对象的创建, SUT中测试方法的启动以及结果验证。 在下面的示例中,我们创建一个请求(在我们的示例中,该请求没有参数),运行fetchUsers(:) Interactor'a方法,然后检查是否在HomeWorkingLogicSpy和HomePresentationLogicSpy中调用了必要的方法。 我们还检查Interactor是否已将从Worker接收的测试数据保存到其DataStore中 。
func testFetchUsers() { let request = HomeModels.FetchUsers.Request() sut.fetchUsers(request) XCTAssertTrue(worker.isCalledFetchUsers, "Not started worker.fetchUsers(:)") XCTAssertTrue(presenter.isCalledPresentFetchedUsers, "Not started presenter.presentFetchedUsers(:)") XCTAssertEqual(sut.users.count, worker.users.count) }
我们将通过类似的结构测试用户的选择。 我们声明变量ExpectationId和ExpectationName ,通过它们我们将比较用户选择的结果。 users变量存储我们分配给Interactor的用户测试列表。 因为 测试方法彼此独立地调用,并且在tearDown()中将数据清零,然后Interactor用户列表为空,我们需要用一些东西填充它。 然后,在调用sut.selectUser(:)之后 ,检查是否在DataStore Interactor'a中分配了该用户,以及该用户是否合适。
func testSelectUser() { let expectationId = 2 let expectationName = "Vasya" let users = [ User(id: 1, name: "Ivan", username: "ivan"), User(id: 2, name: "Vasya", username: "vasya91"), User(id: 3, name: "Maria", username: "maria_love") ] let request = HomeModels.SelectUser.Request(index: 1) sut.users = users sut.selectUser(request) XCTAssertNotNil(sut.selectedUser, "User not selected") XCTAssertEqual(sut.selectedUser?.id, expectationId) XCTAssertEqual(sut.selectedUser?.name, expectationName) }
测试Presenter'a和ViewController'a的原理相同,差异最小。 区别之一是,为了测试ViewController,您将需要创建一个UIWindow并从setUp()中的Storyboard中获取控制器,并在表和集合上创建Spy对象。 但是这些细微差别因需求而异。
为了完整起见,建议您通过本文末尾的链接使您熟悉该项目。
结论
我们已经介绍了在Clean Swift架构上测试应用程序的基本原理。 它与其他体系结构上的测试项目没有本质上的重大差异,所有相同的测试都加倍了,注入和协议相同。 最主要的是不要忘记,每个VIP周期都应该有一个(也只有一个!)责任。 这将使代码更简洁,测试也更加明显。
链接到项目: CleanSwiftTests
撰写文章的帮助: Bastien
系列文章
- Clean Swift体系结构概述
- Clean Swift体系结构中的路由器和数据传递
- 干净的Swift体系结构中的工人
- Clean Swift架构中的单元测试(您在这里)
- 一个简单的在线商店架构Clean Swift的示例