iT邦幫忙

2023 iThome 鐵人賽

DAY 16
0
Mobile Development

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

D16 - 在 iOS 專案加上測試-You need testing {台股小工具 app- test in Combine}

  • 分享至 

  • xImage
  •  

Combine 是一個 Swift 的框架,它提供了一個聲明式的方式來處理非同步的事件和數據流。Combine 可以讓開發者用更簡潔和清晰的代碼來實現複雜的功能,例如網絡請求、用戶輸入、定時器等。Combine 和 SwiftUI 是一個完美的搭配,因為 SwiftUI 是基於 Combine 的反應式 UI 框架。SwiftUI 可以自動更新 UI 的狀態,只要 Combine 發出了相應的事件或數據。這樣就可以避免手動管理 UI 的生命週期和數據綁定,提高了開發效率和用戶體驗。

在前面幾天的文章,我們完成了 StockRecord 與相關的測試,接下來要寫的物件則是存放這堆 StockRecord 的清單,讓 UITableView 或 SwiftUI 的 List/LazyVStack 可以依順序將物件取出

step1: StockTradingRecordStore 物件

Combine 的核心概念是 Publisher 和 Subscriber。Publisher 是一個協議,它定義了一個可以發送一系列的值或者完成或失敗的事件的類型。Subscriber 是一個協議,它定義了一個可以接收 Publisher 發送的值或者完成或失敗的事件的類型。Publisher 和 Subscriber 之間可以通過 subscribe 方法來建立連接,形成一個訂閱關係。當 Publisher 發送一個值或者完成或失敗的事件時,它會通知所有的 Subscriber。

import Combine
import Foundation

class StockTradingRecordStore {
    
    var recordPublisher = CurrentValueSubject<[StockTradingRecord], Never>([])
}

<[StockTradingRecord], Never> 表示這個 publisher 送出的物件是 [StockTradingRecord],第二個參數 Never 的位置,表示如果 publisher send failed 時的 faile type,現在這個位置是 Never。Never 表示這個 send 不會 fail。

step2: 開始寫測試

import XCTest
@testable import TwStockTools

final class StockTradingRecordStoreTests: XCTestCase {

    override func setUpWithError() throws {}

    override func tearDownWithError() throws {}

    func testStockTradingRecordStorePublish() {
        
        let sut = StockTradingRecordStore()
        let publisherExpectation = expectation(description: "wait for publisher in \(#file)")
        
        var receivedRecords: [StockTradingRecord] = []
        let cancellable = sut.recordPublisher
            .dropFirst() /// 因為第一個送出來的是空陣列,要等到變化後,也就是第二個,送出來的才是加進去的 record
            .sink { records in
                receivedRecords = records
                publisherExpectation.fulfill()
            }
    }
}

sut 表示 system under test,在測試程式碼中,常使用 sut 來宣告被測物件。

unit testing 如果要測 async func (eg. api send, 或 data 會在 n 秒後才回傳的狀況),都要下 expecation。如果沒下 expectation,unit testing 不會等待,會直接跑完,為了讓測試正確的反應程式狀況,XCTest 框架提供了 expectation 和 wait,讓 unit testing 可以測試 async func

step3: StockTradingRecordStore 實作加 record 後,用 publisher 進行 send(_)

class StockTradingRecordStore {
    
    var recordPublisher = CurrentValueSubject<[StockTradingRecord], Never>([])
    
    var records: [StockTradingRecord] = [] {
        didSet {
            /// 當 records 一有變化,就 send
            recordPublisher.send(records)
        }
    }
    
    func add(_ record: StockTradingRecord) {
        records.append(record)
    }
}

step4: 在測試寫一個 mock Record,加進 sut 後,將 receivedRecords 拿出來進行測

//  StockTradingRecordStoreTests.swift
private func getRecord() -> StockTradingRecord {
        
        StockTradingRecord(stockID: "0050", stockName: "元大50", tradingSide: .buy, tradingShares: 1000, tradingAmount: 120000, tradingDateStr: "2023-09-07")
    }
func testStockTradingRecordStorePublish() {
        
        let sut = StockTradingRecordStore()
        let publisherExpectation = expectation(description: "wait for publisher in \(#file)")
        
        var receivedRecords: [StockTradingRecord] = []
        let cancellable = sut.recordPublisher
            .dropFirst() /// 因為第一個送出來的是空陣列,要等到變化後,也就是第二個,送出來的才是加進去的 record
            .sink { records in
                receivedRecords = records
                publisherExpectation.fulfill()
            }
        
        let record = getRecord()
        sut.add(record)
        
        wait(for: [publisherExpectation], timeout: 1)
        cancellable.cancel()
        
				// 比對 "0051" 確定 unit testing 會 failed 
        XCTAssertEqual(receivedRecords.first?.stockID, "0051")
    }

step5: 把 XCTAssertEqual 的第二個參數改成 “0050” test pass

https://ithelp.ithome.com.tw/upload/images/20230927/20140622zCfuII1pS6.png

確認所有的 test case 都通過,我們可以對 StockTradingRecordStore 的程式碼,抱持著信心


上一篇
D15 - 在 iOS 專案加上測試-You need testing {單一責則原則 - DateUtility}
下一篇
D17 - 在 iOS 專案加上測試-You need testing {台股小工具 app-與 UI 進行組裝}
系列文
在 iOS 專案上加上 Unit testing - 因為 You need testing32
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言