文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Swift中的HTTP模拟测试示例详解

2023-02-06 15:00

关注

正文

我们已经了解了单个方法如何为通过网络加载请求提供基础。

然而,网络也是开发应用程序时最大的失败点之一,尤其是在单元测试方面。 当我们编写单元测试时,我们希望测试是可重复的:无论我们执行多少次,我们应该总是得到相同的结果。

如果我们的测试涉及实时网络连接,我们无法保证这一点。 由于我们实际网络请求失败的所有原因,我们的单元测试也可能失败。

因此,我们使用模拟对象来模拟网络连接,但实际上提供了一个一致且可重复的外观,我们可以通过它提供虚假数据。

由于我们已将网络接口抽象为单个方法,因此模拟它非常简单。

这是一个始终返回 200 OK 响应的 HTTPLoading 实现:

public class MockLoader: HTTPLoading {
    public func load(request: HTTPRequest, completion: @escaping (HTTPResult) -> Void) {
        let urlResponse = HTTPURLResponse(url: request.url!, statusCode: HTTPStatus(rawValue: 200), httpVersion: "1.1", headerFields: nil)!
        let response = HTTPResponse(request: request, response: urlResponse, body: nil)
        completion(.success(response))
    } 
}

我们可以在任何需要 HTTPLoading 值的地方提供 MockLoader 的实例,发送给它的任何请求都将导致 200 OK 响应,尽管主体为 nil

当我们使用模拟网络连接编写单元测试时,我们并不是在测试网络代码本身。 通过模拟网络层,我们将网络作为变量移除,这意味着网络不是被测试的对象:单元测试检查实验的变量。

StarWarsAPI 类

我们将使用我们在上一篇文章中删除的 StarWarsAPI 类来说明这一原则:

public class StarWarsAPI {
    private let loader: HTTPLoading
    public init(loader: HTTPLoading = URLSession.shared) {
        self.loader = loader
    }
    public func requestPeople(completion: @escaping (...) -> Void) {
        var r = HTTPRequest()
        r.host = "swapi.dev"
        r.path = "/api/people"
        loader.load(request: r) { result in
            // TODO: interpret the result
            completion(...)
        }
    }
}

该类的测试将验证其行为:我们要确保它在不同情况下的行为正确。 例如,我们要确保 requestPeople() 方法在收到 200 OK 响应或 404 Not Found 响应或 500 Internal Server Error 时行为正确。 我们使用 MockLoader 模拟这些场景。 这些测试将使我们有信心在不破坏现有功能的情况下改进 StarWarsAPI 的实现。

MockLoader

为了满足这些需求,我们的 MockLoader 需要:

保证传入的请求是我们在测试中期望的请求 为每个请求提供自定义响应 我个人版本的 MockLoader 大致如下所示:

public class MockLoader: HTTPLoading {
    // typealiases help make method signatures simpler
    public typealias HTTPHandler = (HTTPResult) -> Void
    public typealias MockHandler = (HTTPRequest, HTTPHandler) -> Void
    private var nextHandlers = Array<MockHandler>()
    public override func load(request: HTTPRequest, completion: @escaping HTTPHandler) {
        if nextHandlers.isEmpty == false {
            let next = nextHandlers.removeFirst()
            next(request, completion)
        } else {
            let error = HTTPError(code: .cannotConnect, request: request)
            completion(.failure(error))
        }
    }
    @discardableResult
    public func then(_ handler: @escaping MockHandler) -> Mock {
        nextHandlers.append(handler)
        return self
    }
}

这个 MockLoader 允许我提供如何响应连续请求的个性化实现。 例如:

func test_sequentialExecutions() {
    let mock = MockLoader()
    for i in 0 ..< 5 {
        mock.then { request, handler in
            XCTAssert(request.path, "/(i)")
            handler(.success(...))
        }
    }
    for i in 0 ..< 5 {
        var r = HTTPRequest()
        r.path = "/(i)"
        mock.load(r) { result in
            XCTAssertEqual(result.response?.statusCode, .ok)
        }
    }
}

如果我们在为 StarWarsAPI 类编写测试时使用这个 MockLoader,它可能看起来像这样(我省略了 XCTestExpectations,因为它们与本次讨论没有直接关系):

class StarWarsAPITests: XCTestCase {
    let mock = MockLoader()
    lazy var api: StarWarsAPI = { StarWarsAPI(loader: mock) }()
    func test_200_OK_WithValidBody() {
        mock.then { request, handler in
            XCTAssertEqual(request.path, "/api/people")
            handler(.success())
        }
        api.requestPeople { ...
            // assert that "StarWarsAPI" correctly decoded the response
        }
    }
    func test_200_OK_WithInvalidBody() {
        mock.then { request, handler in
            XCTAssertEqual(request.path, "/api/people")
            handler(.success())
        }
        api.requestPeople { ... 
            // assert that "StarWarsAPI" correctly realized the response was bad JSON
        }
    }
    func test_404() {
        mock.then { request, handler in
            XCTAssertEqual(request.path, "/api/people")
            handler(.success())
        }
        api.requestPeople { ... 
            // assert that "StarWarsAPI" correctly produced an error
        }
    }
    func test_DroppedConnection() {
        mock.then { request, handler in
            XCTAssertEqual(request.path, "/api/people")
            handler(.failure())
        }
        api.requestPeople { ... 
            // assert that "StarWarsAPI" correctly produced an error
        }
    }
    ...
}

当我们编写这样的测试时,我们将 StarWarsAPI 视为一个“黑匣子”:给定特定的输入条件,它是否总是产生预期的输出结果?

我们的 HTTPLoading 抽象使得交换网络堆栈的实现成为一个简单的改变。 我们所做的只是将 MockLoader 传递给初始化程序而不是 URLSession。 这里的关键是意识到,通过使我们的 StarWarsAPI 依赖于接口 (HTTPLoading) 而不是具体化 (URLSession),我们极大地增强了它的实用性并使其更易于单独使用(和测试)。

这种对特定实现的行为定义的依赖将在我们实现框架的其余部分时很好地为我们服务。 在下一篇文章中,我们会将 HTTPLoading 更改为一个类并添加一个属性,该属性将为我们可以想象的几乎所有可能的网络行为提供基础。

以上就是Swift中的HTTP模拟测试示例详解的详细内容,更多关于Swift HTTP模拟测试的资料请关注编程网其它相关文章!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯