为什么使用 DI 模拟比 objective-c 中的模拟对象更好?
Why is mocking with DI better than mocking objects in objective-c?
这个 blog article 说:
While there are sometimes sensible ways to mock out objects without DI
(typically by mocking out class methods, as seen in the OCMock example
above), it’s often flat out not possible. Even when it is possible,
the complexity of the test setup might outweigh the benefits. If
you’re using dependency injection consistently, you’ll find writing
tests using stubs and mocks will be much easier.
但它并没有解释为什么。与简单的 OCMockito 相比,DI(注入符合协议的 id
对象)在哪些情况下更适合在 Objective-C 中进行模拟:
[given([mockArray objectAtIndex:0]) willReturn:@"first"];
[verifyCount(mockArray, times(1)) objectAtIndex:];
?
假设您正在尝试测试一个对象与其子对象之一交互的更复杂的行为。为了确保父对象正确运行,您必须模拟子对象的所有方法,甚至可能跟踪其不断变化的状态。
但如果你这样做,你只是以一种令人困惑和费解的方式编写了一个全新的对象。编写一个全新的对象并告诉父级使用它会更简单。
使用 DI,您可以在运行时注入模型,它不会绑定在您的 类 中,而只会绑定在配置中。
当你想模拟时,你只需创建一个模拟模型并注入它而不是你的真实数据。除了模型之外,您还在一行中更改了实现。
请参阅 here for a hands on example or here 了解其背后的想法。
免责声明:当然你可以模拟模型之外的其他东西,但这可能是最常见的用例。
答案是:并没有更好。只有当您需要一些超级自定义行为时才会更好。
最好的一点是,您不必为每个 class 注入都创建一个 interface/protocol,并且您可以将您真正需要注入的模块限制为 DI更干净,更 YAGNI。
它适用于任何动态语言,或带有反射的语言。仅仅为了单元测试而制造如此多的混乱对我来说是个坏主意。
我注意到当原始 class 执行一些异步操作时,为测试目标创建一个单独的 class 会更容易。
假设您为 UIViewController 编写了一个 测试,它具有使用 AFNetworking 向 API 发出请求的 LoginSystem 依赖项。 LoginSystem 将块参数作为回调。 (UIViewController->LoginSystem->AFNetworking)。
如果您对 LoginSystem 进行模拟,您最终可能会遇到如何触发回调块以在 success/failure 上测试 UIViewController 行为的问题。当我尝试以 MKTArgumentCaptor 结束时检索块参数,然后我不得不在测试文件中调用它。
另一方面,如果您为 LoginSystem 创建一个单独的 class(我们称它为从 LoginSystem 扩展的 LoginSystemStub),您可以 "mock" 3 行代码和外部行为测试文件。我们还应该保持我们的测试文件干净和可读。
另一种情况是 verify() 不适用于检查异步行为。更容易调用 expect(smth2).will.equal(smth)
编辑:
指向 NSError (NSError**) 的指针也不能很好地与 verify() 一起使用,最好创建一个存根 :D
这个 blog article 说:
While there are sometimes sensible ways to mock out objects without DI (typically by mocking out class methods, as seen in the OCMock example above), it’s often flat out not possible. Even when it is possible, the complexity of the test setup might outweigh the benefits. If you’re using dependency injection consistently, you’ll find writing tests using stubs and mocks will be much easier.
但它并没有解释为什么。与简单的 OCMockito 相比,DI(注入符合协议的 id
对象)在哪些情况下更适合在 Objective-C 中进行模拟:
[given([mockArray objectAtIndex:0]) willReturn:@"first"];
[verifyCount(mockArray, times(1)) objectAtIndex:];
?
假设您正在尝试测试一个对象与其子对象之一交互的更复杂的行为。为了确保父对象正确运行,您必须模拟子对象的所有方法,甚至可能跟踪其不断变化的状态。
但如果你这样做,你只是以一种令人困惑和费解的方式编写了一个全新的对象。编写一个全新的对象并告诉父级使用它会更简单。
使用 DI,您可以在运行时注入模型,它不会绑定在您的 类 中,而只会绑定在配置中。
当你想模拟时,你只需创建一个模拟模型并注入它而不是你的真实数据。除了模型之外,您还在一行中更改了实现。
请参阅 here for a hands on example or here 了解其背后的想法。
免责声明:当然你可以模拟模型之外的其他东西,但这可能是最常见的用例。
答案是:并没有更好。只有当您需要一些超级自定义行为时才会更好。
最好的一点是,您不必为每个 class 注入都创建一个 interface/protocol,并且您可以将您真正需要注入的模块限制为 DI更干净,更 YAGNI。
它适用于任何动态语言,或带有反射的语言。仅仅为了单元测试而制造如此多的混乱对我来说是个坏主意。
我注意到当原始 class 执行一些异步操作时,为测试目标创建一个单独的 class 会更容易。
假设您为 UIViewController 编写了一个 测试,它具有使用 AFNetworking 向 API 发出请求的 LoginSystem 依赖项。 LoginSystem 将块参数作为回调。 (UIViewController->LoginSystem->AFNetworking)。
如果您对 LoginSystem 进行模拟,您最终可能会遇到如何触发回调块以在 success/failure 上测试 UIViewController 行为的问题。当我尝试以 MKTArgumentCaptor 结束时检索块参数,然后我不得不在测试文件中调用它。
另一方面,如果您为 LoginSystem 创建一个单独的 class(我们称它为从 LoginSystem 扩展的 LoginSystemStub),您可以 "mock" 3 行代码和外部行为测试文件。我们还应该保持我们的测试文件干净和可读。
另一种情况是 verify() 不适用于检查异步行为。更容易调用 expect(smth2).will.equal(smth)
编辑:
指向 NSError (NSError**) 的指针也不能很好地与 verify() 一起使用,最好创建一个存根 :D