使用 NSURLConnection 进行单元测试
我想测试一段使用网络的代码(具体来说是 NSURLConnection
类)。 代码(让我们称之为 NetworkManager )看起来有点像这样:
- (id) buildConnection
{
// some more code and then:
return [NSURLConnection …];
}
- (void) startNetworkSync
{
id connection = [self buildConnection];
//…
}
在单元测试中,我想摆脱网络,即。 用模拟替换 NSURLConnection
对象。 我该怎么做呢?
我尝试创建 NetworkManager
的部分模拟,它将用存根替换 buildConnection
方法。 问题是 OCMock 完成的部分模拟仅来自外部世界的存根消息– 从 startNetworkSync
发送 buildConnection
会调用原始方法,而不是存根。
我还尝试通过类别对 NetworkManager 类进行猴子修补。 这是有效的,我可以轻松地通过其他代码覆盖 buildConnection
方法,并用存根替换真正的 NSURLConnection
。 问题是我发现没有简单的方法可以在测试中获取存根连接 - 该连接是 NetworkManager 的私有部分。
然后,我可以对 NetworkManager 进行子类化,重写 buildConnection 方法并为创建的连接添加一个实例变量和一个访问器。 不过,这似乎有很多代码。
你会如何解决这个问题? 我正在寻找一种解决方案,使 NetworkManager 类设计保持干净,并且在测试中不需要太多魔法或太多代码。
I want to test a piece of code that uses network (the NSURLConnection
class, to be specific). The code (let’s call it NetworkManager
) looks a bit like this:
- (id) buildConnection
{
// some more code and then:
return [NSURLConnection …];
}
- (void) startNetworkSync
{
id connection = [self buildConnection];
//…
}
In the unit test I would like to get rid of the networking, ie. replace the NSURLConnection
object by a mock. How do I do this?
I’ve tried creating a partial mock of the NetworkManager
that would replace the buildConnection
method by a stub. The problem is that partial mocks as done by OCMock only stub messages from the outside world – sending buildConnection
from the startNetworkSync
invokes the original method, not the stub.
I have also tried monkey-patching the NetworkManager
class through a category. This works, I can easily override the buildConnection
method by other code and replace the real NSURLConnection
with a stub. The problem is that I found no simple way I could get the stubbed connection in the test – the connection is a private part of the NetworkManager
.
Then I could subclass the NetworkManager
, override the buildConnection
method and add an instance variable plus an accessor for the created connection. This seems like a lot of code, though.
How would you solve this? I am looking for a solution that keeps the NetworkManager
class design clean and does not require much magic nor much code in the test.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
这就是依赖注入旨在解决的问题; 如果您使用
startNetworkSyncWithConnection:(NSURLConnection*)
代替,您可以使用模拟连接轻松测试该方法。 如果您不想更改客户端的 API,您甚至可以将startNetworkSync
保留为包装器,除了使用[self buildConnection]
作为调用该新方法之外什么也不做。争论。This is the kind of thing dependency injection is designed to solve; if you use
startNetworkSyncWithConnection:(NSURLConnection*)
instead you can easily test the method with a mock connection. If you don't want to change the API for your clients you could even keepstartNetworkSync
as a wrapper that does nothing but call that new method with[self buildConnection]
as the argument.我修改了 OCMock 以支持真正的部分模拟,请参阅 GitHub 上的存储库。
I modified OCMock to support real partial mocks, see the repo on GitHub.
我最近使用的另一个解决方案是完全抽象网络接口。 如果该类需要来自网络的一些数据,它可能会与某些可以明确建模为协议的服务器服务进行交互:
然后您将拥有一个真正的 HTTP 实现和一个测试实现。 这意味着更多的工作,但也意味着更好的可测试性。 测试不那么脆弱而且更方便,因为测试网络层可以做任何你需要的事情。
Another solution I have used recently is to completely abstract the networking interface. If the class needs some data from the network, it probably interacts with some server service that can be explictly modelled as a protocol:
And then you’ll have a real HTTP implementation and a testing one. This means more work, but also much better testability. The tests are less brittle and much more convenient, since the testing network layer can do whatever you need.