Swift中的HTTP模擬測(cè)試示例詳解
正文
我們已經(jīng)了解了單個(gè)方法如何為通過(guò)網(wǎng)絡(luò)加載請(qǐng)求提供基礎(chǔ)。
然而,網(wǎng)絡(luò)也是開(kāi)發(fā)應(yīng)用程序時(shí)最大的失敗點(diǎn)之一,尤其是在單元測(cè)試方面。 當(dāng)我們編寫單元測(cè)試時(shí),我們希望測(cè)試是可重復(fù)的:無(wú)論我們執(zhí)行多少次,我們應(yīng)該總是得到相同的結(jié)果。
如果我們的測(cè)試涉及實(shí)時(shí)網(wǎng)絡(luò)連接,我們無(wú)法保證這一點(diǎn)。 由于我們實(shí)際網(wǎng)絡(luò)請(qǐng)求失敗的所有原因,我們的單元測(cè)試也可能失敗。
因此,我們使用模擬對(duì)象來(lái)模擬網(wǎng)絡(luò)連接,但實(shí)際上提供了一個(gè)一致且可重復(fù)的外觀,我們可以通過(guò)它提供虛假數(shù)據(jù)。
由于我們已將網(wǎng)絡(luò)接口抽象為單個(gè)方法,因此模擬它非常簡(jiǎn)單。
這是一個(gè)始終返回 200 OK
響應(yīng)的 HTTPLoading
實(shí)現(xiàn):
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
的實(shí)例,發(fā)送給它的任何請(qǐng)求都將導(dǎo)致 200 OK
響應(yīng),盡管主體為 nil
。
當(dāng)我們使用模擬網(wǎng)絡(luò)連接編寫單元測(cè)試時(shí),我們并不是在測(cè)試網(wǎng)絡(luò)代碼本身。 通過(guò)模擬網(wǎng)絡(luò)層,我們將網(wǎng)絡(luò)作為變量移除,這意味著網(wǎng)絡(luò)不是被測(cè)試的對(duì)象:?jiǎn)卧獪y(cè)試檢查實(shí)驗(yàn)的變量。
StarWarsAPI 類
我們將使用我們?cè)谏弦黄恼轮袆h除的 StarWarsAPI
類來(lái)說(shuō)明這一原則:
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(...) } } }
該類的測(cè)試將驗(yàn)證其行為:我們要確保它在不同情況下的行為正確。 例如,我們要確保 requestPeople()
方法在收到 200 OK
響應(yīng)或 404 Not Found
響應(yīng)或 500 Internal Server Error
時(shí)行為正確。 我們使用 MockLoader
模擬這些場(chǎng)景。 這些測(cè)試將使我們有信心在不破壞現(xiàn)有功能的情況下改進(jìn) StarWarsAPI
的實(shí)現(xiàn)。
MockLoader
為了滿足這些需求,我們的 MockLoader
需要:
保證傳入的請(qǐng)求是我們?cè)跍y(cè)試中期望的請(qǐng)求 為每個(gè)請(qǐng)求提供自定義響應(yīng) 我個(gè)人版本的 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 } }
這個(gè) MockLoader
允許我提供如何響應(yīng)連續(xù)請(qǐng)求的個(gè)性化實(shí)現(xiàn)。 例如:
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) } } }
如果我們?cè)跒?StarWarsAPI
類編寫測(cè)試時(shí)使用這個(gè) MockLoader
,它可能看起來(lái)像這樣(我省略了 XCTestExpectations
,因?yàn)樗鼈兣c本次討論沒(méi)有直接關(guān)系):
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 } } ... }
當(dāng)我們編寫這樣的測(cè)試時(shí),我們將 StarWarsAPI
視為一個(gè)“黑匣子”:給定特定的輸入條件,它是否總是產(chǎn)生預(yù)期的輸出結(jié)果?
我們的 HTTPLoading
抽象使得交換網(wǎng)絡(luò)堆棧的實(shí)現(xiàn)成為一個(gè)簡(jiǎn)單的改變。 我們所做的只是將 MockLoader
傳遞給初始化程序而不是 URLSession
。 這里的關(guān)鍵是意識(shí)到,通過(guò)使我們的 StarWarsAPI 依賴于接口 (HTTPLoading) 而不是具體化 (URLSession),我們極大地增強(qiáng)了它的實(shí)用性并使其更易于單獨(dú)使用(和測(cè)試)。
這種對(duì)特定實(shí)現(xiàn)的行為定義的依賴將在我們實(shí)現(xiàn)框架的其余部分時(shí)很好地為我們服務(wù)。 在下一篇文章中,我們會(huì)將 HTTPLoading
更改為一個(gè)類并添加一個(gè)屬性,該屬性將為我們可以想象的幾乎所有可能的網(wǎng)絡(luò)行為提供基礎(chǔ)。
以上就是Swift中的HTTP模擬測(cè)試示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Swift HTTP模擬測(cè)試的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- Swift中Optional值的鏈?zhǔn)秸{(diào)用學(xué)習(xí)筆記
- Swift 中如何使用 Option Pattern 改善可選項(xiàng)的 API 設(shè)計(jì)
- Swift?Package?技巧及混編兼容問(wèn)題詳解
- Swift重構(gòu)自定義空等運(yùn)算符 “??=” 實(shí)例
- Swift?重構(gòu)重載運(yùn)算符示例解析
- Swift HTTP加載請(qǐng)求Loading Requests教程
- Swift中的HTTP請(qǐng)求體Request Bodies使用示例詳解
- Swift中的可選項(xiàng)Optional解包方式實(shí)現(xiàn)原理
相關(guān)文章
快速排序算法在Swift編程中的幾種代碼實(shí)現(xiàn)示例
快速排序是一種不穩(wěn)定的排序,存在著優(yōu)化空間,這里我們來(lái)看快速排序算法在Swift編程中的幾種代碼實(shí)現(xiàn)示例:2016-07-07Swift3遷移至Swift4可能遇到的問(wèn)題小結(jié)
每當(dāng)看到新的編程語(yǔ)言我總是會(huì)有相當(dāng)大的興趣,所以下面這篇文章主要給大家介紹了關(guān)于Swift3遷移至Swift4可能遇到的問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-06-06Swift?Error重構(gòu)的基礎(chǔ)示例詳解
這篇文章主要為大家介紹了Swift?Error基礎(chǔ)錯(cuò)誤處理的方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11Swift中Optional值的鏈?zhǔn)秸{(diào)用學(xué)習(xí)筆記
這篇文章主要介紹了Swift中Optional值的鏈?zhǔn)秸{(diào)用學(xué)習(xí)筆記,Optional鏈?zhǔn)荢wift入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2016-07-07switch循環(huán)所支持的數(shù)據(jù)類型案例分析
這篇文章主要介紹了switch循環(huán)所支持的數(shù)據(jù)類型,本文通過(guò)實(shí)際案例講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06