要采取的方法是使用一个测试双重检查您的upload
方法进行正确的网络调用。您将对网络库进行异步调用,该网络库可能为URLSession
或可能是另一个库,如AlamoFire。它应该无关紧要你的upload
方法哪个库正在使用。
为了达到这个目的,你想避免直接使用URLSession,并使用一个符合接口的包装器,然后你可以在你的测试中进行模拟。这意味着您的代码将在运行时使用不同的网络类实现,而不是在测试时间,并且您将根据需要“注入”正确的代码。
例如,你可以有这个接口来您的网络库:
protocol NetworkRequesting {
func post(data: Data, url: URL)
}
用下面的真正落实到在运行时使用:
struct NetworkRequester: NetworkRequesting {
func post(data: Data, url: URL) {
let session = URLSession()
let task = session.uploadTask(with: URLRequest(url: url), from: data)
task.resume()
}
}
然而,在测试时,您可以使用下面的模拟,而不是:
class MockNetworkRequester: NetworkRequesting {
var didCallPost = false
var spyPostData: Data? = nil
var spyPostUrl: URL? = nil
func post(data: Data, url: URL) {
didCallPost = true
spyPostData = data
spyPostUrl = url
}
}
,然后给下面的类测试:
class ImageUploader {
let networkRequester: NetworkRequesting
init(networkRequester: NetworkRequesting) {
self.networkRequester = networkRequester
}
func upload(image: UIImage, url: URL) {
}
}
可以测试的upload
实施像这样:
class UploadImageTests: XCTestCase {
func test_uploadCallsPost() {
let mockNetworkRequester = MockNetworkRequester()
let uploader = ImageUploader(networkRequester: mockNetworkRequester)
uploader.upload(image: UIImage(), url: URL(string:"http://example.com")!)
XCTAssert(mockNetworkRequester.didCallPost)
}
}
目前,由于upload
做什么,测试将失败,但是如果你把被测以下进级,测试将通过:
func upload(image: UIImage, url: URL) {
guard let otherUrl = URL(string:"https://example.org") else { return }
networkRequester.post(data: Data(), url: otherUrl)
}
这就是你的第一个TDD周期。很明显,它还没有像你期望的那样表现出来,所以你需要编写另一个测试来确保使用的URL是你期望的,或者传递的数据是你期望的。
有很多方法可以让您的代码在运行时使用真正的网络请求者,您可以让init方法使用默认参数值使其使用NetworkRequester
或使用静态工厂方法创建它,还有其他选项,如控制反转,这远远超出了这个答案的范围。
要记住的重要事情是,你要测试你做出正确的电话到网络框架,你没有测试的网络框架。我喜欢保持我的协议接口非常具有声明性,在任何框架中传递请求所需的东西,但是您可能会发现您更喜欢更接近金属,并基本反映URLSession的实现 - 这取决于您,还有更多在我看来,这是一门艺术而不是科学。
我喜欢你的方法,我现在要试试:)谢谢你花时间给我这个答案 – Helbo