Xcode 在排版上最適合邊寫測試邊開發的方法,就是左右併排。一邊寫測試程式碼,另一邊寫程式實作。
開 StockTradingRecord 檔案與 StockTradingRecordTests 測試檔案,開好後將多餘的模版程式碼刪除掉,大概會長這樣。然後,我們就可以開始寫測試了。
在 test 那邊加上一個 func,注意 func 要有 test 前綴才能被 XCTest 認知到這是 test func
func testStockTradingRecordInit() {
}
從前面的 wireframe 來看,我們會需要 stockID, stockName, tradingSide, tradingShares, tradingAmount。這個物件的宣告可以先這樣子寫。
extension StockTradingRecord {
enum TradingSide {
case buy
case sell
}
}
struct StockTradingRecord {
let stockID: String
let stockName: String
let tradingSide: TradingSide
/// 成交股數
let tradingShares: Int
/// 成交金額
let tradingAmount: Int
}
第一步,要讓測試 failed,用來確定錯誤的時候 unit testing 可以正確的告訴你錯誤
func testStockTradingRecordInit() {
let model = StockTradingRecord(stockID: "1101", stockName: "台泥", tradingSide: .buy, tradingShares: 1000, tradingAmount: 35000)
/// 讓 test failed
XCTAssertEqual(model.stockID, "1102")
}
然後,確認 test failed 後,把 test 改成可以通過的狀態
func testStockTradingRecordInit() {
let model = StockTradingRecord(stockID: "1101",
stockName: "台泥",
tradingSide: .buy,
tradingShares: 1000,
tradingAmount: 35000)
XCTAssertEqual(model.stockID, "1101")
XCTAssertEqual(model.stockName, "台泥")
XCTAssertEqual(model.tradingSide, .buy)
XCTAssertEqual(model.tradingShares, 1000)
XCTAssertEqual(model.tradingAmount, 35000)
}
Voila! 資料 Model 的測試就完成了。
但從 StockTradingRecord 的宣告來看, tradingShares 和 tradingAmount 是可以塞入負數的。所以如果寫 init 帶入負數的 data model testing,是會通過的。
func testStockTradingRecordSell() {
let model = StockTradingRecord(stockID: "1101",
stockName: "台泥",
tradingSide: .buy,
tradingShares: -2000,
tradingAmount: -70000)
XCTAssertEqual(model.stockID, "1101")
XCTAssertEqual(model.stockName, "台泥")
XCTAssertEqual(model.tradingSide, .buy)
XCTAssertEqual(model.tradingShares, -2000)
XCTAssertEqual(model.tradingAmount, -70000)
}
好,問題來了,tradingShares, tradingAmount 可不可以輸入負數?如果不可以,那應該把 tradingShares 和 tradingAmount 設定成 UInt 嗎?
我們先把宣告改成 UInt 並跑一次測試
當我們宣告成 UInt 的時候 Unit testing 是沒辦法執行,因為 Swift 是強型別語言,當參數的型別不合格的時候,在 compile time 就會停下來,Unit testing 本身也是一個 target,所以在跑 unit testing 時,就會 build failed。
以買進股票為例,帳戶的持股數會增加,金流方向是從約定帳戶匯出到券商帳戶。所以如果開發的時候定義,約定帳戶的金額增加的動作為 +,金額減少的動作為 -。用 Int 就合理。
但一筆交易,會在[買進]/[賣出] 中二者擇一 (除權息的紀錄先會是其他 feature),開發時然可以決定[買進]/[賣出]的行為本身來決定金流和股數變化的方向。
所以在這邊,並沒辦法指出唯一解法,在實務上的設計,比較好的做法是將 spec 列出,程式邏輯列出,讓不同的平台(iOS, Android, Web, 後端) 都用同一個邏輯,然後各自使用自己端的語言實作。
因這個專案的開發者只有我,所以並沒有第二個人需要討論,這裡的策略是先讓 model 的 tradingShares, tradingAmount 設成 Int,並寫下 Unit testing。在有 Unit testing 的狀況下,即使未來我需要修改,我仍然可以對修改具有信心。
struct StockTradingRecord {
let stockID: String
let stockName: String
let tradingSide: TradingSide
/// 成交股數
let tradingShares: Int
/// 成交金額
let tradingAmount: Int
}
func testStockTradingRecordSell() {
let model = StockTradingRecord(stockID: "1101",
stockName: "台泥",
tradingSide: .buy,
tradingShares: -2000,
tradingAmount: -70000)
XCTAssertEqual(model.stockID, "1101")
XCTAssertEqual(model.stockName, "台泥")
XCTAssertEqual(model.tradingSide, .buy)
XCTAssertEqual(model.tradingShares, -2000)
XCTAssertEqual(model.tradingAmount, -70000)
}