iT邦幫忙

2023 iThome 鐵人賽

DAY 21
0
Mobile Development

在 iOS 專案上加上 Unit testing - 因為 You need testing系列 第 21

D21 - 在 iOS 專案加上測試-You need testing {測試 async/await function}

  • 分享至 

  • xImage
  •  

在 Swift 5.5 引入了 async await 這個功能,讓我們可以更方便地處理非同步的任務。async await 可以讓我們用同步的方式寫出非同步的程式碼,避免了回呼地獄(callback hell)和金字塔(pyramid of doom)的問題。async await 的基本用法是這樣的:

  • 如果一個函數需要執行非同步的任務,我們可以在宣告時加上 async 關鍵字,表示它是一個異步函數(asynchronous function)。
  • 如果我們要呼叫一個異步函數,我們可以在呼叫時加上 await 關鍵字,表示我們要等待它完成後才繼續執行下面的程式碼。await 只能在異步函數裡面使用。
  • 如果我們要在異步函數裡面處理錯誤,我們可以用 try await 來呼叫可能會拋出錯誤的異步函數,並用 do catch 來捕捉錯誤。

step1: 開出 APIClient 檔案與 APIClientTests

專案資料夾的結構可以長這樣

https://ithelp.ithome.com.tw/upload/images/20231002/201406228JqHzqQ5bU.png

step2: 測試 APIClient

/// APIClient.swift
struct APIClient {
    
}
///  APIClientTests.swift
import XCTest
@testable import TwStockTools

final class APIClientTests: XCTestCase {
    
    var sut: APIClient!

    override func setUpWithError() throws {
        sut = APIClient()
    }

    override func tearDownWithError() throws {
        sut = nil
    }

}

step3: 在 APIClient.swift 檔案裡,加上這段 URLSessionProtocol,並宣告這個 protocol 需有下面這個 func

/// APIClient.swift
protocol URLSessionProtocol {
    
    func data(for request: URLRequest, delegate: URLSessionTaskDelegate?) async throws -> (Data, URLResponse)
}

step4: 讓 URLSession conform URLSessionProtocol

/// APIClient.swift
extension URLSession: URLSessionProtocol {}

step5: 在 Unit 上加上 Mock object URLSessionProtocolMock

class URLSessionProtocolMock: URLSessionProtocol {
    
    var dataForDelegateReturnValue: (Data, URLResponse)?
    
    var dataForDelegateRequest: URLRequest?
    
    func data(for request: URLRequest, delegate: URLSessionTaskDelegate?) async throws -> (Data, URLResponse) {
        
        dataForDelegateRequest = request
        
        guard let dataForDelegateReturnValue = dataForDelegateReturnValue else {
            fatalError()
        }
        
        return dataForDelegateReturnValue
    }
}

step6: 在 APIClient.swift 使用 URLSessionProtocolMock()

/// APIClient.swift
/// 測試每日收盤價與月均價
    /// https://openapi.twse.com.tw/v1/exchangeReport/STOCK_DAY_AVG_ALL
    func testClosePriceAndMonthlyAvgPrice() async throws {
        
        let url = try XCTUnwrap(URL(string: "https://openapi.twse.com.tw/v1/exchangeReport/STOCK_DAY_AVG_ALL"))
        let urlSessionMock = URLSessionProtocolMock()
        
				let expectedStockInfos = [StockDailyInfoElement(code: "0050", name: "元大台灣50", closingPrice: "123.55", monthlyAveragePrice: "125.16")]
        
				urlSessionMock.dataForDelegateReturnValue = (try JSONEncoder().encode(expectedStockInfos), HTTPURLResponse(url: url, statusCode: 200, httpVersion: "HTTP/1.1", headerFields: nil)!)
        
        sut.session = urlSessionMock
    }

sut.session 現在會報錯,將 session 補上

step7: 將 APIClient 補上 session property 與 getStockClosPriceList()

struct APIClient {
    
    lazy var session: URLSessionProtocol = URLSession.shared
    
    func getStockClosPriceList() async throws -> [StockDailyInfoElement] {
        return []
    }
}

step8: 開始測 getStockClosPriceList 然後會得到 error。開始修改 client

//  APIClient.swift
func getStockClosPriceList() async throws -> [StockDailyInfoElement] {
        
        guard let url = URL(string: "https://openapi.twse.com.tw/v1/exchangeReport/STOCK_DAY_AVG_ALL") else {
            return []
        }
        
        let request = URLRequest(url: url)
        let (data, _) = try await session.data(for: request, delegate: nil)
        
        let stocks = try JSONDecoder().decode([StockDailyInfoElement].self, from: data)

        return stocks
    }

step9: 再跑一次測試,可以看到測試全部通過,不過,這個 test 沒有處理到 error 的部分,下一篇來處理 Error

https://ithelp.ithome.com.tw/upload/images/20231002/201406220ePErDTgr5.png


上一篇
D20 - 在 iOS 專案加上測試-You need testing {台股小工具 app-測 UserDefaults part2}
下一篇
D22 - 在 iOS 專案加上測試-You need testing {handling errors}
系列文
在 iOS 專案上加上 Unit testing - 因為 You need testing32
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言