与杜鹃和代码生成保持一致

请注意 ,由于README为该库提供了很好的介绍,因此本文不会涉及Cuckoo的基础知识。

您是否编写单元测试? 你应该。 在Objective-C中为我的测试编写模拟程序似乎要容易得多。 OCMock充分利用了Objective-C的动态特性,使模拟变得轻而易举。 但是,OCMock网站上有这样的说法:

令人失望。 准确。

好吧,出发寻找替代方案。 我能找到的最好的东西是SwiftMock,但这带来了更加尖锐的失望:

Swift似乎缺乏反射性,这意味着我们注定要手动做事并一次又一次地重写相同类型的代码。

因此,我采取了自己的模拟游戏。 它没有那么坏。 真。 我基于Mock协议并使用Swift的基本反射构建了自己的Mock框架,在TDD的世界中,所有人都感到很高兴。

当您编写大量代码以及应附带的所有测试时,为模拟编写样板的所有时间都很累。 我渴望更高效的东西。

然后,我被介绍给杜鹃。

Cuckoo是Swift的一个模拟框架,它使用代码生成来创建您自己编写的样板。 它具有很多功能,我认为这些功能过于繁琐和重复,无法自己完成。 它还可以确保一致性,这是我在手工模拟世界中非常缺乏的东西。

代码生成与反思

大多数编程语言使用反射来动态执行某些事情,否则我们将不得不为其编写重复的样板。 OCMock使用反射来创建模拟对象。 实际上,它不会为您要模拟的每种类型创建一个新类。 反射允许像Objective-C这样的语言来动态创建模拟,检查是否相等以及执行JSON序列化。

到目前为止,在Swift中这并不是一件容易的事。 Swift语言避免了动态行为,并鼓励使用更安全的方法。 另一方面,代码生成非常适合Swift的类型安全性。 您拥有可以被人类阅读并可以由编译器验证的代码。

起初我觉得很奇怪。 我可能没有写样板,但它仍然存在。 由工具编写并隐藏在后台。 但这似乎是像Swift这样的语言的前进方向。 诸如Sourcery和SwiftGen之类的框架使您可以为这些任务采用代码生成,否则可以通过反射来完成。

缺乏反射和强大的类型安全性意味着在减少您花在单调任务上的时间和更多时间在应用程序重要部分上的工作时,代码生成是新的入门方法。

布谷鸟使用代码生成比反射方法具有许多优势,例如类型检查,优化以及逐步调试生成的代码的能力。

不用发疯地使用杜鹃

布谷鸟已经变得更加易于使用,并且需要反复尝试。 这是到目前为止我已经学到的一些见解。

熟悉ParameterMatchers

看这个例子:

 stub(mock) { stub in 
when(stub.greetWithMessage("Hello world")).thenDoNothing()
}

当传递字符串“ Hello World”时,我们将greetWithMessagegreetWithMessage函数的调用行为。 重要的是在此处注意,代码实际上并不期望使用String对象。 它期望一个Matchable

Matchable协议用于验证参数是否与一组条件匹配。 之所以可以将“ Hello World”字符串传递给上面的示例,是因为Cuckoo善意地将String扩展为符合Matchable的要求:

 extension String: Matchable { 
public var matcher: ParameterMatcher {
return equal(to: self)
}
}

这意味着可以将期望String匹配的任何模拟函数传递给String的实例。

我们如何匹配自己的自定义对象?

假设我们有一个logInWithCredentials函数,该函数将UserCredentials对象作为参数。 当Cuckoo生成模拟时,我们需要将loginWithCredentials对象传递给loginWithCredentials函数。 我们可以像扩展字符串一样扩展UserCredentials对象:

 extension UserCredentials: Matchable { 
public var matcher: ParameterMatcher {
return ParameterMatcher { $0 == self }
}
}

现在我们可以像这样对函数进行存根:

 let testCredentials = UserCredentials(name: "Bradley", password: "pass") 
stub(mock) { stub in
when(stub.logInWithCredentials(testCredentials)).thenDoNothing()
}

请记住,除了比较相等性之外,您还可以执行更复杂的检查。

 let matchesUserStartingWithB = ParameterMatcher { 
$0.name.first! == "B"
}
 stub(mock) { stub in 
when(stub.logInWithCredentials(matchesUserStartingWithB))
.thenDoNothing()
}

在编译测试之前运行脚本

Cuckoo的文档建议您将Run Script阶段添加到测试目标,以生成包含模拟的GeneratedMocks.swift文件。

您可以避免一些愚蠢的错误,从而节省一些时间: 实际编译测试之前 ,请确保已放置Cuckoo的Run Script阶段。

当事情不对劲的时候,布谷鸟会抛出一些相当隐蔽的编译错误。 众所周知,布谷鸟有时会破坏Swift编译器并给它带来分段错误,在这种情况下,您不会在导致破坏的行上得到有用的红色文本。 确保仔细查看所有编译器错误,并阅读消息以查明问题所在。

应用标准的调试技术:将问题分解为最简单的部分。 消除所有可能性,直到您知道问题所在。 简化您要尝试执行的操作,直到使其生效,然后从那里开始进行构建。

如果类型不匹配,则错误可能特别严重:

这里发生的是loginWithCredentials方法期望UserCredentials对象具有Matchable对象,但我却给它传递了一个String。

在这种情况下,或者当您有复杂的表达式并且怀疑它可能影响编译器时,它有助于消除类型推断,直到事情再次开始起作用。 因此,而不是:

 stub(mock) { stub in 
when(stub.logInWithCredentials(ParameterMatcher {
$0.userId == 123
}))
.then { response in print(response.statusCode) } }

我会更明确地指定类型,例如:

 let matchesUser123 = ParameterMatcher { 
$0.userId == 123
}
 stub(mock) { stub in 
when(stub.logInWithCredentials(matchesUser123)
.then { (response: LoginResponse) in
print(response.statusCode)
}
}

呼叫验证最后,而不是先

Facepalm时刻到了。

来自OCMock背景,在实际调用生产代码之前,您可能习惯于调用OCMExpect

 - (void)testLoggingInCallsLoginService { 
OCMExpect([mockLoginService loginWithCredentials:OCMOCK_ANY]);
[classUnderTest startLoginProcess];
}

使用Cuckoo进行此操作,您的测试将始终失败。 调用时会检查要verify的调用,因此请在调用其他代码之后而不是之前调用它。

 func testLoggingInCallsLoginService() { 
classUnderTest.startLoginProcess()
verify(mockLoginService).loginWithCredentials(any())
}

如果您无法击败他们,请将他们排除在外

由于无法继承结构,因此无法嘲笑结构(您可以代之以协议)。 有些类也可能很棘手,例如UIViewController的复杂子类。 有时,我在文件中有一个协议,希望Cuckoo为其生成模拟,并且在同一文件中有另一个对象,我希望Cuckoo忽略它。 您可以使用--exclude选项轻松完成此操作:

 ${PODS_ROOT}/Cuckoo/run generate --testable "$PROJECT_NAME" \ 
--exclude "ThatStructIWantToExclude,\
ThatOtherObjectThatBreaksMyBuild" \
--output "${OUTPUT_FILE}" \
"$INPUT_DIR/FileName1.swift" \
"$INPUT_DIR/FileName2.swift"

--exclude选项是一个相对较新的选项,它解决了许多其他问题,这些问题否则将导致需要还原为手动滚动的模拟。 让这成为经常阅读文档的好课程。

合理使用它

杜鹃可以节省时间。 花费一些时间来了解它的来龙去脉,您可能会发现它是完成这项工作的最有效工具。 如果不是,请不要使用它。

在某些情况下,快速手动模拟是最好的方法,当它是较快速的选择时,我仍然会这样做。 务实,做对您和您的团队有用的事情。

编写有价值的软件。 测试您的代码。 玩得开心。 祝你雨燕愉快!