我正在尝试对视图控制器类中的两个函数进行单元测试。这两个功能将分别创建用户和登录用户。我的测试与用户界面无关。
到目前为止,我只需要创建一个在调用其中一个函数时通过的测试。目的是在不更改当前视图控制器实现的情况下做到这一点,并将其全部保留在测试类/函数中。
我该怎么办?
谢谢
答案 0 :(得分:2)
我假设一个视图控制器具有调用“创建用户”或“登录用户”的方法。 (您说这与用户界面无关,但是如果这是触发因素,我们可以轻松测试按钮的点击。)
class MyViewController: UIViewController {
func triggeringMethod() {
// Calls one of the two methods below
}
func createUser() {
// Does stuff
}
func signInUser() {
// Does stuff
}
}
听起来您想测试流程,但不是效果。也就是说,您要测试“ createUser()
被调用了吗?”有几种获取所需内容的方法,但是更好的方法将需要您更改视图控制器的实现。
Michael Feathers的有效处理旧版代码的标准技巧是“子类和覆盖方法”。让我们从这里开始。在测试代码中,我们可以制作
class TestableMyViewController: MyViewController {
override func createUser() {
}
override func signInUser() {
}
}
到目前为止,这是消除这些方法的影响的一种方法。但是我们现在可以尝试添加模拟技术!迅速东京谈话。
class TestableMyViewController: MyViewController {
var createUserCallCount = 0
var signInUserCallCount = 0
override func createUser() {
createUserCallCount += 1
}
override func signInUser() {
signInUserCallCount += 1
}
}
现在您可以调用触发方法,并检查呼叫计数。
(您可能需要进行更改:类不能为final
。方法不能为private
。)
虽然这是一个不错的起点,但不要就此止步。我们创建的是“部分模拟”。那是我们保留了大多数功能的地方,但是模拟了其中的两种方法。这是要避免的事情。原因是我们最终得到了将生产代码和测试代码混合在一起的类。在您无意中测试测试代码而不是测试生产代码的情况下,结束最终结果太容易了。
部分模拟清楚表明,我们缺少边界。视图控制器做得太多。 “创建用户”和“登录用户”的实际工作应由另一种类型(甚至可能是2种类型)执行。在Swift中,我们可以使用协议定义此边界。这样,生产代码可以使用真正的功能,而对于测试代码,我们可以注入模拟。
这意味着生产代码应避免自己决定谁来做实际工作。相反,我们应该告诉由谁来做。这样,测试可以提供其他工作人员。从外部指定这些依赖关系称为“依赖关系注入”。
另一个选项可让我们完全避免模拟。无需测试是否调用了某些内容,我们可以在枚举中描述所需的效果。然后我们可以定义效果
enum Effect {
case createUser(CreateUserRequestModel)
case signInUser(SignInUserRequestModel)
}
它不会调用触发方法createUser()
或signInUser()
,而是会调用一个委托。 (另一种选择是传递闭包而不是指定委托。)
protocol Delegate {
perform(_ effect: Effect)
}
然后使用触发方法
delegate?.perform(.createUser(parameters))
这意味着要由实际的委托人将这些枚举值转换为实际的工作。但这使测试易于编写。我们所需要做的就是提供一个捕获Effect
值的测试实现。