测试驱动上传方法?

Test Driven Upload method?

我正处于尝试使用测试驱动开发的情况。 我在 Swift、Xcode、Apple、IOS、TDD 甚至我用于开发的 macbook 方面都没有经验。基本上我是一个非常陌生的情况下的 .Net 开发人员。

我目前的问题是由于我对如何进行测试 void 方法的单元测试一无所知。

我正在尝试制作一种将图像发送到服务器的方法。

但我的问题是我不知道如何测试没有 return 值的方法。

我想我的方法将与此类似:

public func Upload(_ image: UIImage)

我想我需要实现某个版本的 URLSession,最终将不得不调用 resume() 方法。但是如何在不调用网络的情况下测试此方法是否正在执行它应该执行的操作?然后我如何进行集成测试,我可以看到预期的结果实际上是一个上传到服务器的文件? 目前服务器将在我正在开发的计算机上,但实际软件将 运行 来自我已经发布的 testIphone。

我已经在网上搜索了好几天了,我遇到的最好的是这个 link http://swiftdeveloperblog.com/image-upload-with-progress-bar-example-in-swift/ 但它只是接近我想象的点点滴滴,这将成为解决方案的一部分,而不是对所述解决方案的测试。

我认为重要的是要补充一点,我非常反对为了测试目的而创建太多的复杂性。测试应该简单直接。

采取的方法是使用测试替身来检查您的 upload 方法是否进行了正确的网络调用。您将对网络库进行异步调用,它可能是 URLSession 或另一个库,例如 AlamoFire。使用哪个库对您的 upload 方法无关紧要。

为此,您希望避免直接使用 URLSession,并使用符合随后可以在测试中模拟的接口的包装器。这意味着您的代码将在运行时使用与测试时不同的网络实现 class,并且您将 "inject" 根据需要使用正确的实现。

例如,您可以将此接口连接到您的网络库:

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 待测:

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 什么都不做,但如果将以下内容放入被测 class 中,测试将通过:

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 的实现 - 这取决于你,以及更多在我看来,与其说是一门科学,不如说是一门艺术。