RxSwift_v1.0筆記——16 Testing with RxTest

RxSwift_v1.0筆記——16 Testing with RxTest

100分

以上這是給你的袍榆,為了表?yè)P(yáng)你沒(méi)有略過(guò)此章節(jié)。研究表明開發(fā)者略過(guò)編寫測(cè)試用例有兩個(gè)原因:

  1. 他們只會(huì)寫沒(méi)有錯(cuò)誤的代碼
  2. 編寫測(cè)試用例不好玩

如果你只是第一個(gè)原因丸相,那么你被錄用了!如果你也同意第二個(gè)原因,那么讓我給你介紹一下我的小朋友:RxTest妨退》砘担基于之所以你開始閱讀這本書并很激動(dòng)的將RxSwift用于你的APP項(xiàng)目中的所有原因锭魔,RxTest(和RxBlocking)也會(huì)很快讓你對(duì)用RxSwift 代碼 編寫測(cè)試用例感興趣。它們會(huì)提供一個(gè)簡(jiǎn)潔的API路呜,讓編寫測(cè)試用例變得簡(jiǎn)單而有趣迷捧。

這個(gè)章節(jié)將會(huì)給你介紹RxTest织咧,稍后是RxBlocking,用來(lái)寫測(cè)試

本章將向您介紹RxTest以及RxBlocking漠秋,通過(guò)針對(duì)多個(gè)RxSwift操作編寫測(cè)試笙蒙,并針對(duì)RxSwift產(chǎn)品代碼編寫測(cè)試。

開始 300

這個(gè)章節(jié)的啟動(dòng)設(shè)計(jì)名字叫Testing庆锦,它包含一個(gè)掌上APP捅位,可以為輸入的16進(jìn)制顏色代碼提供紅,綠搂抒,藍(lán)色值和顏色名字(若有)艇搀。運(yùn)行安裝后,打開這個(gè)workspace并運(yùn)行求晶。你可以看到這個(gè)APP用 rayWenderlichGreen開始焰雕,但是你可以輸入任意16進(jìn)制顏色代碼并獲得rgb和顏色名字。

這個(gè)APP是使用MVVM設(shè)計(jì)模式組織起來(lái)的芳杏,你可以在MVVM章節(jié)學(xué)習(xí)MVVM的相關(guān)知識(shí)矩屁。簡(jiǎn)單來(lái)說(shuō)就是邏輯代碼被封裝在視圖模型中,視圖控制器用來(lái)控制視圖爵赵。除了枚舉流行的顏色名稱之外档插,整個(gè)應(yīng)用程序都運(yùn)行在這個(gè)邏輯上,您將在本章稍后部分中寫出測(cè)試:

// Convert hex text to color
color = hexString.asObservable()
  .map { hex in
    guard hex.characters.count == 7 else { return .clear }
    let color = UIColor(hex: hex)
    return color
  }
  .asDriver(onErrorJustReturn: .clear)

// Convert the color to an rgb tuple
rgb = color.asObservable()
  .map { color in
    var red: CGFloat = 0.0
    var green: CGFloat = 0.0
    var blue: CGFloat = 0.0

    color.getRed(&red, green: &green, blue: &blue, alpha: nil)
    let rgb = (Int(red * 255.0), Int(green * 255.0), Int(blue * 255.0))
    return rgb
  }
  .asDriver(onErrorJustReturn: (0, 0, 0))

// Convert the hex text to a matching name
colorName = hexString.asObservable()
  .map { hexString in
    let hex = String(hexString.characters.dropFirst())

    if let color = ColorName(rawValue: hex) {
      return "\(color)"
    } else {
      return "--"
    }
  }
  .asDriver(onErrorJustReturn: "")

在投入這個(gè)代碼到testing之前亚再,編寫兩個(gè)針對(duì)RxSwift操作的測(cè)試用例對(duì)學(xué)習(xí)RxTest 是很用幫助的郭膛。

Note:這個(gè)章節(jié)是假設(shè)你很熟悉在iOS系統(tǒng)中用XCTest編寫單元測(cè)試,如果你是新手氛悬,可以找下我們的視頻課程(原失效)https://www.raywenderlich.com/150521/updated-course-ios-unit-ui-testing

用RxTest測(cè)試操作 301

Note:因?yàn)镾wift包管理的問(wèn)題则剃,原“RxTests”已經(jīng)重命名為RxTest。因此如果你在野外(out in the wild)看到了“RxTests”如捅,它很可能是指RxTest棍现。

RxTest是RxSwift的獨(dú)立庫(kù)。 它在RxSwift repo內(nèi)托管(host)镜遣,但需要單獨(dú)的pod安裝和導(dǎo)入己肮。 RxTest為測(cè)試RxSwift代碼提供了許多有用的補(bǔ)充,例如TestScheduler悲关,它是一個(gè)虛擬時(shí)間scheduler谎僻,可以精確控制測(cè)試時(shí)間線性操作,包括 next(::)寓辱, completed(::)艘绍,和 error(::),可以在測(cè)試中的指定時(shí)間將這些事件添加到observables秫筏。 它還添加了冷和熱observables诱鞠,你可以把它想象成冷熱三明治挎挖。不,不是真的航夺。

什么的是熱和冷的observables蕉朵? 301

RxSwift用了大量的篇幅去簡(jiǎn)化你的Rx代碼,并且他們有辦法讓你明白熱的和冷的區(qū)別阳掐,當(dāng)談到observables始衅,在RxSwift里更多的考慮的是observables的特點(diǎn)是而不是具體類型。這有點(diǎn)像一點(diǎn)補(bǔ)充的細(xì)節(jié)锚烦,但是它值得你多加關(guān)注,因?yàn)樵赗xSwift 的測(cè)試內(nèi)容以外是沒(méi)有這么多討論熱的和冷的observable的帝雇。

熱observables:

  • 使用資源是否有訂閱者涮俄。
  • 產(chǎn)生元素是否有訂閱者。
  • 主要用于狀態(tài)類型尸闸,如Variable彻亲。

冷observables:

  • 僅僅在訂閱時(shí)消耗資源
  • 有訂閱者才產(chǎn)生元素
  • 主要使用異步操作,例如網(wǎng)絡(luò)吮廉。

你稍后寫的單元測(cè)試將使用熱observables苞尝。 但是,如果您需要使用另一個(gè)需求宦芦,請(qǐng)了解不同之處宙址。

打開在TestingTests組中的TestingOperators.swift。在類 TestingOperators的頂部定義了兩個(gè)屬性:

var scheduler: TestScheduler!
var subscription: Disposable!

scheduler是 TestScheduler的一個(gè)實(shí)例调卑,你將使用在每個(gè)test中抡砂,并且 subscription將保持你每個(gè)test中的訂閱。改變setUP()的定義:

override func setUp() {
  super.setUp()

  scheduler = TestScheduler(initialClock: 0)
}

在setUP()方法中恬涧,在每個(gè)測(cè)試用例開始都會(huì)調(diào)用它注益。你用TestScheduler (initialClock: 0)初始化一個(gè)新的scheduler。它的意思是你希望在測(cè)試開始時(shí)啟動(dòng)測(cè)試 scheduler溯捆。這很快就會(huì)變得有意義丑搔。

現(xiàn)在改變 tearDown()的定義:

override func tearDown() {

  scheduler.scheduleAt(1000) { 
    self.subscription.dispose()
  }

  super.tearDown()
}

tearDown()在每個(gè)測(cè)試完成時(shí)調(diào)用。在它里面提揍,在1000毫秒后你調(diào)度測(cè)試訂閱的銷毀啤月。你寫的每個(gè)測(cè)試將運(yùn)行至少1秒,因此在1秒后銷毀測(cè)試的訂閱是安全的劳跃。

現(xiàn)在朋友顽冶,是時(shí)候?qū)憸y(cè)試了。在 tearDown()的定義后面增加一個(gè)新的test到TestingOperators:

//1
func testAmb() {

  //2
  let observer = scheduler.createObserver(String.self)
}

你做了以下內(nèi)容:

  1. 像所有使用XCTest的tests一樣售碳,方法名必須以test開頭强重。你建立了一個(gè)名叫amb的測(cè)試绞呈。
  2. 你使用scheduler的 createObserver(_:)方法與String類型的示意創(chuàng)建了一個(gè)觀察者

觀察者將記錄它接收到的每個(gè)事件的時(shí)間戳,就像在RxSwift中的debug操作间景,但不會(huì)打印任何輸出佃声。在Combining Operators章節(jié)你已經(jīng)學(xué)習(xí)了amb操作。amb被用在兩個(gè)observables之間倘要,哪個(gè)observable首先發(fā)射圾亏,它就只傳播它發(fā)射的事件。你需要?jiǎng)?chuàng)建兩個(gè)observables封拧。增加下面代碼到test:

//1
let observableA = scheduler.createHotObservable([
  // 2
  next(100, "a)"),
  next(200, "b)"),
  next(300, "c)")
  ])
// 3
let observableB = scheduler.createHotObservable([
  // 4
  next(90, "1)"),
  next(200, "2)"),
  next(300, "3)")
  ])

這個(gè)代碼做了:

  1. 使用 scheduler的createHotObservable(_:)創(chuàng)建一個(gè)observableA志鹃。
  2. 使用next(::)方法在指定的時(shí)間(毫秒)添加.next事件到observableA上 ,第二個(gè)參數(shù)作為值傳遞泽西。
  3. 創(chuàng)建 名為observableB的熱observable
  4. 用規(guī)定的值在指定的時(shí)間增加 .next事件到 observableB

要知道amb將只傳播第一個(gè)發(fā)射事件的observable的事件曹铃。你能夠猜到這個(gè)這個(gè)測(cè)試就是為了測(cè)這個(gè)。

為了測(cè)試這個(gè)捧杉,增加下面的代碼來(lái)使用amb操作并分配結(jié)果到一個(gè)本地常量:

let ambObservable = observableA.amb(observableB)

Option-click在ambObservable上陕见,你將看到它是 Observable<String>類型。

Note:如果你的Xcode又出了毛病(on the fritz)味抖,你可能會(huì)看到<<error type>>评甜,不要擔(dān)心,運(yùn)行測(cè)試時(shí)Xcode會(huì)識(shí)別它仔涩。

下一步忍坷,你需要告訴scheduler來(lái)調(diào)度在指定時(shí)間的動(dòng)作。增加下面代碼:

scheduler.scheduleAt(0) { 
  self.subscription = ambObservable.subscribe(observer)
}

這里你調(diào)度了 ambObservable在0時(shí)訂閱到observer熔脂,并分配訂閱到 subscription屬性承匣。這樣一來(lái),tearDown()將銷毀訂閱锤悄。

為了確實(shí)地開始(kick off)測(cè)試然后確認(rèn)結(jié)果韧骗,增加下面代碼:

scheduler.start()

這將啟動(dòng)虛擬時(shí)間調(diào)度程序,并且觀察者將收到您通過(guò)amb操作指定的.next事件零聚。

現(xiàn)在你能夠收集和分析結(jié)果袍暴。輸入以下代碼:

let results = observer.events.map {
  $0.value.element!
}

在觀察者的事件屬性上你使用map訪問(wèn)每個(gè)事件的元素。現(xiàn)在你能斷言這些實(shí)際的結(jié)果通過(guò)增加下面代碼來(lái)匹配你期望的結(jié)果

XCTAssertEqual(results, ["1)", "2)", "3)"])

點(diǎn)擊函數(shù) testAmb()左側(cè)溝槽(gutter)中的鉆石按鈕來(lái)執(zhí)行測(cè)試隶症。

當(dāng)測(cè)試結(jié)束后政模,你應(yīng)該看到完成了(又叫(aka)通過(guò))

通常你將創(chuàng)建一個(gè)負(fù)面測(cè)試來(lái)補(bǔ)充這個(gè),例如測(cè)試接收到的結(jié)果與你知道的他們應(yīng)該不是這個(gè)的結(jié)果不一致蚂会。這章節(jié)完成之前你還有更多的測(cè)試要寫淋样,因此要快速測(cè)試你的測(cè)試是否工作,按以下內(nèi)容更改斷言:

XCTAssertEqual(results, ["1)", "2)", "No you didn't!"])

再次運(yùn)行測(cè)試確保出現(xiàn)以下錯(cuò)誤信息:

XCTAssertEqual failed: ("["1)", "2)", "3)"]") is not equal to ("["1)", "2)", "No you didn't!"]")

撤銷上面的改變?cè)龠\(yùn)行測(cè)試確保它再次通過(guò)胁住。

你花了一整章節(jié)來(lái)學(xué)習(xí)過(guò)濾操作趁猴,為什么不測(cè)試一個(gè)呢刊咳?增加下面的測(cè)試到 TestingOperators,它與 testAmb()保持了一樣的格式:

func testFilter() {
  // 1
  let observer = scheduler.createObserver(Int.self)
  // 2
  let observable = scheduler.createHotObservable([
    next(100, 1),
    next(200, 2),
    next(300, 3),
    next(400, 2),
    next(500, 1)
    ])
  // 3
  let filterObservable = observable.filter {
    $0 < 3
  }
  // 4
  scheduler.scheduleAt(0) {
    self.subscription = filterObservable.subscribe(observer)
  }
  // 5
  scheduler.start()
  // 6
  let results = observer.events.map {
    $0.value.element!
  }
  // 7
  XCTAssertEqual(results, [1, 2, 2, 1])
}

從頭開始:

  1. 創(chuàng)建一個(gè)觀察者儡司,時(shí)間類型為Int娱挨。
  2. 創(chuàng)建一個(gè)熱observable,它每秒schedulers一個(gè).next事件捕犬,共5秒跷坝。
  3. 創(chuàng)建 filterObservable來(lái)保存在observable上使用過(guò)濾的結(jié)果,過(guò)濾條件為判斷元素的值小于3碉碉。
  4. 在0時(shí)開始調(diào)度訂閱并分配它到訂閱屬性以便它將在 tearDown()被銷毀柴钻。
  5. 啟動(dòng)scheduler。
  6. 收集結(jié)果垢粮。
  7. 斷言你期望的結(jié)果贴届。

點(diǎn)擊這個(gè)測(cè)試旁溝槽的鉆石圖標(biāo)運(yùn)行測(cè)試,你將得到綠勾指示了測(cè)試成功足丢。

這些測(cè)試已經(jīng)同步粱腻。當(dāng)你想測(cè)試異步操作庇配,你有兩個(gè)選擇斩跌。你將首先學(xué)習(xí)容易的一個(gè),使用RxBlocking捞慌。

使用RxBlocking 306

RxBlocking是封裝(housed)在RxSwift repo內(nèi)部的另一個(gè)庫(kù)耀鸦,像RxTest一樣,有它自己的pod且必須分開導(dǎo)入啸澡。它的主要目的是通過(guò)它的 toBlocking(timeout:)方法袖订,轉(zhuǎn)換一個(gè)observable到 BlockingObservable。這樣做會(huì)阻塞當(dāng)前線程嗅虏,直到observable終止洛姑,或者如果指定了一個(gè)超時(shí)值(默認(rèn)情況下為零),并且在observable終止之前達(dá)到超時(shí)皮服,則會(huì)引發(fā)RxError.timeout錯(cuò)誤楞艾。 這基本上將異步操作轉(zhuǎn)換為同步操作,使測(cè)試變得更加容易龄广。

增加下面在RxBlocking內(nèi)的三行測(cè)試代碼到 TestingOperators來(lái)測(cè)試 toArray操作:

func testToArray() {
  // 1
  let scheduler = ConcurrentDispatchQueueScheduler(qos: .default)
  // 2
  let toArrayObservable = Observable.of("1)",
                                        "2)").subscribeOn(scheduler)
  // 3
  XCTAssertEqual(try! toArrayObservable.toBlocking().toArray(), ["1)",
                                                                 "2)"])
}

它做了的如下:

  1. 使用默認(rèn)的服務(wù)質(zhì)量硫眯,創(chuàng)建并發(fā)scheduler來(lái)運(yùn)行異步測(cè)試
  2. 創(chuàng)建observable來(lái)保持在scheduler上,訂閱到兩個(gè)字符串的observable的結(jié)果择同。
  3. 對(duì)toArrayObservable調(diào)用toBlocking()的結(jié)果使用toArray两入,并斷言toArray的返回值等于預(yù)期結(jié)果。

toBlocking()轉(zhuǎn)換 toArrayObservable為一個(gè)阻塞observable敲才,阻止由scheduler產(chǎn)生的線程裹纳,直到它終止择葡。運(yùn)行測(cè)試你應(yīng)該看到成功。僅用三行代碼就測(cè)試了一個(gè)異步操作——哇痊夭!你將用簡(jiǎn)潔的RxBlocking做更多工作刁岸,但現(xiàn)在是時(shí)候離開操作的測(cè)試并寫一些針對(duì)(against)應(yīng)用產(chǎn)品代碼的測(cè)試。

測(cè)試RxSwift的產(chǎn)品代碼 307

首先打開在Testing組中的ViewModel.swift她我。在頂部虹曙,你將看到一些屬性定義:

let hexString = Variable<String>("")
let color: Driver<UIColor>
let rgb: Driver<(Int, Int, Int)>
let colorName: Driver<String>

hexString接收來(lái)至視圖控制器的輸入。color番舆,rgb和colorName是輸出酝碳,視圖控制器將綁定到視圖。在視圖模型的初始中恨狈,通過(guò)轉(zhuǎn)換另一個(gè)observable并把返回結(jié)果作為Driver疏哗。這是顯示在章節(jié)開始處的代碼。

接下來(lái)初始化的是一個(gè)枚舉類型禾怠,定義到模型的常見的顏色名返奉。

enum ColorName: String {
  case aliceBlue = "F0F8FF"
  case antiqueWhite = "FAEBD7"
  case aqua = "0080FF"
  // And many more...

現(xiàn)在打開ViewController.swift,聚焦到 viewDidLoad()的實(shí)現(xiàn)上吗氏。

override func viewDidLoad() {
  super.viewDidLoad()

  configureUI()

  guard let textField = self.hexTextField else { return }

  textField.rx.text.orEmpty
    .bindTo(viewModel.hexString)
    .disposed(by: disposeBag)

  for button in buttons {
    button.rx.tap
      .bindNext {
        var shouldUpdate = false

        switch button.titleLabel!.text! {
        case "?":
          textField.text = "#"
          shouldUpdate = true
        case "←" where textField.text!.characters.count > 1:
          textField.text = String(textField.text!.characters.dropLast())
          shouldUpdate = true
        case "←":
          break
        case _ where textField.text!.characters.count < 7:
          textField.text!.append(button.titleLabel!.text!)
          shouldUpdate = true
        default:
          break
        }

        if shouldUpdate {
          textField.sendActions(for: .valueChanged)
        }
      }
      .disposed(by: self.disposeBag)
  }

  viewModel.color
    .drive(onNext: { [unowned self] color in
      UIView.animate(withDuration: 0.2) {
        self.view.backgroundColor = color
      }
    })
    .disposed(by: disposeBag)

  viewModel.rgb
    .map { "\($0.0), \($0.1), \($0.2)" }
    .drive(rgbTextField.rx.text)
    .disposed(by: disposeBag)

  viewModel.colorName
    .drive(colorNameTextField.rx.text)
    .disposed(by: disposeBag)
}

從頭開始:

  1. 綁定文本框的文本(或者一個(gè)空的字符串)到視圖模型的hexString輸入observable
  2. 循環(huán)遍歷按鈕出口的集合芽偏,綁定tap并轉(zhuǎn)換按鈕的標(biāo)題來(lái)決定怎樣更新文本框的文字,與文本框是否應(yīng)該發(fā)送valueChanged控制事件弦讽。
  3. 使用視圖模型的color驅(qū)動(dòng)來(lái)更新視圖的背景顏色污尉。
  4. 使用視圖模型的rgb驅(qū)動(dòng)來(lái)更新rbgTextField的文本。
  5. 使用實(shí)體模型的coloName驅(qū)動(dòng)來(lái)更新colorNameTextField的文本往产。

通過(guò)預(yù)覽app是如何工作的被碗,你現(xiàn)在能夠針對(duì)它來(lái)寫測(cè)試。在TestingTests組內(nèi)打開TestingViewModel.swift仿村,按如下修改setUP()的實(shí)現(xiàn):

override func setUp() {
  super.setUp()
  viewModel = ViewModel()
  scheduler = ConcurrentDispatchQueueScheduler(qos: .default)
}

這里锐朴,你分配app ViewModel類的一個(gè)實(shí)體給viewModel屬性,用默認(rèn)服務(wù)質(zhì)量的一個(gè)并發(fā)scheduler給scheduler屬性蔼囊。

現(xiàn)在你可以開始針對(duì)app的視圖模型來(lái)寫測(cè)試了焚志。首先,你將使用傳統(tǒng)的XCTest API編寫一個(gè)異步測(cè)試压真。增加視圖模型顏色驅(qū)動(dòng)(使用傳統(tǒng)方式)的測(cè)試到TestingViewModel:

func testColorIsRedWhenHexStringIsFF0000_async() {
  let disposeBag = DisposeBag()
  // 1
  let expect = expectation(description: #function)
  // 2
  let expectedColor = UIColor(red: 1.0, green: 0.0, blue: 0.0, alpha:
    1.0)
  // 3
  var result: UIColor!
}

你做了以下工作:

  1. 創(chuàng)建一個(gè)稍后實(shí)現(xiàn)的預(yù)期娩嚼。
  2. 創(chuàng)建 expectedColor等于紅色的預(yù)期的測(cè)試結(jié)果。
  3. 定義結(jié)果稍后分配滴肿。

這僅僅是起始代碼≡牢颍現(xiàn)在將以下代碼添加到測(cè)試以訂閱視圖模型的color驅(qū)動(dòng)程序:

// 1
viewModel.color.asObservable()
  .skip(1)
  .subscribe(onNext: {
    // 2
    result = $0
    expect.fulfill()
  })
  .disposed(by: disposeBag)
// 3
viewModel.hexString.value = "#ff0000"
// 4
waitForExpectations(timeout: 1.0) { error in
  guard error == nil else {
    XCTFail(error!.localizedDescription)
    return
  }
  // 5
  XCTAssertEqual(expectedColor, result)
}
  1. 創(chuàng)建一個(gè)訂閱到視圖模型的color驅(qū)動(dòng)。注意你略過(guò)了第一個(gè)元素,因?yàn)轵?qū)動(dòng)將在訂閱上重放初始元素贵少。
  2. 分配.next事件元素到result并在expect上調(diào)用fulfill()呵俏。
  3. 在視圖模型的hexString上增加一個(gè)新的值輸入給observable(一個(gè)Variable)。
  4. 用1秒來(lái)超時(shí)等待expectation的完成滔灶,并在閉包中為error提供guard
  5. 斷言期望的color等于實(shí)際的result普碎。

很簡(jiǎn)單但有點(diǎn)冗長(zhǎng)。運(yùn)行測(cè)試確保它通過(guò)录平。

現(xiàn)在使用RxBlocking來(lái)實(shí)現(xiàn)同樣的事情:

func testColorIsRedWhenHexStringIsFF0000() {
  // 1
  let colorObservable =
    viewModel.color.asObservable().subscribeOn(scheduler)
  // 2
  viewModel.hexString.value = "#ff0000"
  // 3
  do {
    guard let result = try colorObservable.toBlocking(timeout:
      1.0).first() else { return }
    XCTAssertEqual(result, .red)
  } catch {
    print(error)
  }
}
  1. 創(chuàng)建coloObservable來(lái)保存訂閱在并發(fā)scheduler上的observable結(jié)果麻车。
  2. 在視圖模型的hexString上增加一個(gè)新值輸入給observable。
  3. 使用guard來(lái)選擇將調(diào)用toBlocking()的結(jié)果與1秒的超時(shí)綁定斗这,如果拋出动猬,捕獲并打印錯(cuò)誤,然后斷言實(shí)際的結(jié)果與預(yù)期的匹配表箭。

運(yùn)行測(cè)試確保它是成功的赁咙。這個(gè)測(cè)試本質(zhì)上與前一個(gè)相同。你只是不需要那么辛苦免钻。

接下來(lái)彼水,添加此代碼以測(cè)試視圖模型的rgb驅(qū)動(dòng)為給定的hexString輸入發(fā)出預(yù)期的紅色,綠色和藍(lán)色值:

func testRgbIs010WhenHexStringIs00FF00() {
  // 1
  let rgbObservable =
    viewModel.rgb.asObservable().subscribeOn(scheduler)
  // 2
  viewModel.hexString.value = "#00ff00"
  // 3
  let result = try! rgbObservable.toBlocking().first()!
  XCTAssertEqual(0 * 255, result.0)
  XCTAssertEqual(1 * 255, result.1)
  XCTAssertEqual(0 * 255, result.2)
}
  1. 創(chuàng)建rgbObservable來(lái)保存在scheduler上的訂閱极舔。
  2. 在視圖模型的hexString上增加一個(gè)新值輸入給observable凤覆。
  3. 檢索在rgbObservable上調(diào)用toBlocking的第一個(gè)結(jié)果,然后斷言每個(gè)值與期望的匹配姆怪。

01轉(zhuǎn)換到0255僅僅是為了匹配測(cè)試名并讓接下來(lái)的事情更加容易叛赚。運(yùn)行這個(gè)測(cè)試確保它成功通過(guò)澡绩。

還有一個(gè)要測(cè)試的驅(qū)動(dòng)程序 將此測(cè)試添加到TestingViewModel稽揭,來(lái)測(cè)試視圖模型的colorName驅(qū)動(dòng)為給定的hexString輸入發(fā)出正確的元素:

func testColorNameIsRayWenderlichGreenWhenHexStringIs006636() {
  // 1
  let colorNameObservable =
    viewModel.colorName.asObservable().subscribeOn(scheduler)
  // 2
  viewModel.hexString.value = "#006636"
  // 3
  XCTAssertEqual("rayWenderlichGreen", try!
    colorNameObservable.toBlocking().first()!)
}
  1. 創(chuàng)建observable
  2. 增加測(cè)試值。
  3. 斷言實(shí)際的結(jié)果來(lái)匹配期望的結(jié)果肥卡。

這是我想起了短語(yǔ)”漂洗和重復(fù)“溪掀,這是一個(gè)好的方式。寫測(cè)試就是應(yīng)該簡(jiǎn)單步鉴。按Command-U運(yùn)行在項(xiàng)目中的所有測(cè)試揪胃,所有測(cè)試都應(yīng)該通過(guò)。

使用RxText and RxBlocking寫測(cè)試是使用RxSWift和RxCocoa寫數(shù)據(jù)和UI綁定(以及其他)氛琢。這章沒(méi)有挑戰(zhàn)喊递,因?yàn)槟銓⒃贛VVM章中做更多的視圖模型測(cè)試。測(cè)試真高興阳似!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末骚勘,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌俏讹,老刑警劉巖当宴,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異泽疆,居然都是意外死亡户矢,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門殉疼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)梯浪,“玉大人,你說(shuō)我怎么就攤上這事瓢娜∏ぃ” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵恋腕,是天一觀的道長(zhǎng)抹锄。 經(jīng)常有香客問(wèn)我,道長(zhǎng)荠藤,這世上最難降的妖魔是什么伙单? 我笑而不...
    開封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮哈肖,結(jié)果婚禮上吻育,老公的妹妹穿的比我還像新娘。我一直安慰自己淤井,他們只是感情好布疼,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著币狠,像睡著了一般游两。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上漩绵,一...
    開封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天贱案,我揣著相機(jī)與錄音,去河邊找鬼止吐。 笑死宝踪,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的碍扔。 我是一名探鬼主播瘩燥,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼不同!你這毒婦竟也來(lái)了厉膀?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎站蝠,沒(méi)想到半個(gè)月后汰具,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡菱魔,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年留荔,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片澜倦。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡聚蝶,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出藻治,到底是詐尸還是另有隱情碘勉,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布桩卵,位于F島的核電站验靡,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏雏节。R本人自食惡果不足惜胜嗓,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望钩乍。 院中可真熱鬧辞州,春花似錦、人聲如沸寥粹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)涝涤。三九已至媚狰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間妄痪,已是汗流浹背哈雏。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工楞件, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留衫生,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓土浸,卻偏偏與公主長(zhǎng)得像罪针,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子黄伊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容