如何创建在触发功能时通过的测试?

时间:2018-12-01 05:44:52

标签: ios swift unit-testing mocking xctest

我正在尝试对视图控制器类中的两个函数进行单元测试。这两个功能将分别创建用户和登录用户。我的测试与用户界面无关。

到目前为止,我只需要创建一个在调用其中一个函数时通过的测试。目的是在不更改当前视图控制器实现的情况下做到这一点,并将其全部保留在测试类/函数中。

我该怎么办?

谢谢

1 个答案:

答案 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值的测试实现。