【问题标题】:Test Driven Upload method?测试驱动上传方法?
【发布时间】:2017-07-23 07:51:56
【问题描述】:

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

我目前的问题源于我对如何进行单元测试来测试 void 方法的无知。

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

但我的问题是我不知道如何测试不返回值的方法。

我想我的方法会是这样的:

public func Upload(_ image: UIImage)

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

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

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

【问题讨论】:

    标签: ios swift3 xcode8 xctest


    【解决方案1】:

    采取的方法是使用测试替身来检查您的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 的实现——这取决于你,还有更多在我看来,一门艺术而不是一门科学。

    【讨论】:

    • 我喜欢你的方法,我现在要试试 :) 感谢你抽出时间给我这个答案
    猜你喜欢
    • 1970-01-01
    • 2012-07-25
    • 1970-01-01
    • 2011-03-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多