網站首頁 編程語言 正文
正文
我們已經了解了單個方法如何為通過網絡加載請求提供基礎。
然而,網絡也是開發應用程序時最大的失敗點之一,尤其是在單元測試方面。 當我們編寫單元測試時,我們希望測試是可重復的:無論我們執行多少次,我們應該總是得到相同的結果。
如果我們的測試涉及實時網絡連接,我們無法保證這一點。 由于我們實際網絡請求失敗的所有原因,我們的單元測試也可能失敗。
因此,我們使用模擬對象來模擬網絡連接,但實際上提供了一個一致且可重復的外觀,我們可以通過它提供虛假數據。
由于我們已將網絡接口抽象為單個方法,因此模擬它非常簡單。
這是一個始終返回 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(/* 200 OK with some valid JSON */)) } 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(/* 200 OK but some mangled JSON */)) } 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(/* 404 Not Found */)) } api.requestPeople { ... // assert that "StarWarsAPI" correctly produced an error } } func test_DroppedConnection() { mock.then { request, handler in XCTAssertEqual(request.path, "/api/people") handler(.failure(/* HTTPError of some kind */)) } api.requestPeople { ... // assert that "StarWarsAPI" correctly produced an error } } ... }
當我們編寫這樣的測試時,我們將 StarWarsAPI
視為一個“黑匣子”:給定特定的輸入條件,它是否總是產生預期的輸出結果?
我們的 HTTPLoading
抽象使得交換網絡堆棧的實現成為一個簡單的改變。 我們所做的只是將 MockLoader
傳遞給初始化程序而不是 URLSession
。 這里的關鍵是意識到,通過使我們的 StarWarsAPI 依賴于接口 (HTTPLoading) 而不是具體化 (URLSession),我們極大地增強了它的實用性并使其更易于單獨使用(和測試)。
這種對特定實現的行為定義的依賴將在我們實現框架的其余部分時很好地為我們服務。 在下一篇文章中,我們會將 HTTPLoading
更改為一個類并添加一個屬性,該屬性將為我們可以想象的幾乎所有可能的網絡行為提供基礎。
原文鏈接:https://juejin.cn/post/7196889066830266424
相關推薦
- 2022-09-07 python+selenium?實現掃碼免密登錄示例代碼_python
- 2023-02-06 python?wordcloud庫實例講解使用方法_python
- 2022-06-27 Vscode的SSH插件遠程連接Linux的實現步驟_其它綜合
- 2022-05-07 Python中list列表的賦值方法及遇到問題處理_python
- 2023-04-06 C語言中單鏈表的基本操作(創建、銷毀、增刪查改等)_C 語言
- 2022-06-22 C語言詳解Z字形變換排列的實現_C 語言
- 2022-09-20 基于C語言實現隨機點名器(附源碼)_C 語言
- 2022-06-17 go語言beego框架jwt身份認證實現示例_Golang
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支