Quickのmatcherとして使われているNimbleはとても柔軟に書くことができます。
NimbleのREADMEも充実しているので、そんなに躓くことなく書くことができるのですが、
- でもやっぱりちょっと躓いたところがある
- すぐに動かせるサンプルがあった方が楽
だと思ったのでサンプルを作ってみました。
基本的には上記2項目に該当するもののサンプルを書いているので、これでNimbleの全てがわかるわけではありません。
https://github.com/akatsuki174/QuickSample
(以前投稿した記事のサンプルも混じっていますが、いずれにしてもQuick, Nimbleのサンプルになっています。あとでリファクタリング、多少のサンプルの追加はしようと思っています。)
この記事はそのサンプルのポイント解説という位置づけで書いていきます。
Comparisons
数値など大小の比較ができるものに対して使うものになります。
関数 | 記号 | 意味 |
---|---|---|
beLessThan | < | actualがexpectより小さかったらテストが通ります。 |
beLessThanOrEqualTo | <= | actualがexpectより小さいか同じだったらテストが通ります。 |
beGreaterThan | > | actualがexpectより大きかったらテストが通ります。 |
beGreaterThanOrEqualTo | >= | actualがexpectより大きいか同じだったらテストが通ります。 |
let one = 1
let five = 5
expect(one).to(beLessThan(five))
expect(one) < five
expect(one).to(beLessThanOrEqualTo(five))
expect(one) <= five
expect(five).to(beGreaterThan(one))
expect(five) > one
expect(five).to(beGreaterThanOrEqualTo(one))
expect(five) >= one
上記は整数値の場合は何も問題なく比較することができます。
しかし浮動小数点数の等値比較は必ずしも成功するわけではありません。そのため「多少誤差があっても同じ数値だとみなし」てテストすることもできます。
そのような場合はbeCloseTo
や≈
を使います。
許容する誤差は下記withinの部分やdeltaの部分に書きます。
ちなみにこの時Floatではダメで、Doubleにする必要があります。
let five:Double = 5
let five_point_one:Double = 5.1
expect(five_point_one).to(beCloseTo(five, within: 0.2))
expect(5.0001) ≈ five // デフォルトのdeltaの0.0001が適用される
let delta = 0.2
expect(five_point_one) ≈ (five, delta)
expect(five_point_one) ≈ five ± delta
expect(five_point_one) == five ± delta
また、同じようにして配列の要素もテストすることができます。
expect([0.00001, 2.00001]) ≈ [0.0, 2.0]
expect([0.1, 2.1]).to(beCloseTo([0.0, 2.0], within: 0.2))
これに関しては一度公式に書かれているコード(以下)をそのまま貼り付けてテストしてみたのですが、test failedになってしまいました。
expect([0.0, 2.0]) ≈ [0.0001, 2.0001]
expect([0.0, 2.0]).to(beCloseTo([0.1, 2.1], within: 0.1))
デフォルトのdeltaの0.0001以内に入っているので通過しそうではあるのですが。。。
結局、私のサンプルのようにdeltaを少し大きくしたら無事テスト通過しました。
これに関してはもうちょっと調べてみたいと思っています。
Types/Classes
関数 | 意味 |
---|---|
beAnInstanceOf | あるインスタンスがあるクラスのインスタンスであった場合はテストが通ります。 |
beAKindOf | あるインスタンスがあるクラスのインスタンスである、もしくはそのクラスのサブクラスのインスタンスであった場合はテストが通ります。 |
これらの関数を使う場合、インスタンスはObjective-Cのオブジェクトである必要があります。なので、NSObjectを継承するか、@objc
プレフィックスをつける必要があります。
expect(animal).to(beAnInstanceOf(Animal))
expect(cat).to(beAKindOf(Animal))
※AnimalはNSObjectを継承、CatはAnimalを継承。
Truthiness
ある値の真偽、もしくは値が入っているかどうかを検証する関数になります。
関数 | 意味 |
---|---|
beTruthy | nilではないか、trueの場合はテストが通ります。 |
beTrue | trueの時のみテストが通ります。 |
beFalsy | nilであるか、falseの場合はテストが通ります。 |
beFalse | falseの時のみテストが通ります。 |
beNil | nilの場合にテストが通ります。 |
let bl:Bool? = true
expect(bl).to(beTruthy())
expect(bl).to(beTrue())
let str:String? = "hoge"
expect(str).to(beTruthy())
let bl:Bool? = false
expect(bl).to(beFalsy())
expect(bl).to(beFalse())
let str:String? = nil
expect(str).to(beFalsy())
let bl:Bool? = nil
expect(bl).to(beNil())
Swift Error Handling
throwError
を使えば正しくエラーハンドリングが行われているかどうかのテストも作ることができます。
let str = ""
expect{ try self.printStr(str) }.to(throwError())
expect{ try self.printStr(str) }.to(throwError { (error: ErrorType) in
expect(error._domain).to(equal("the value is empty"))
expect(error._code).to(equal(-1))
})
expect{ try self.requestLogin(str) }.to(throwError(RequestError.Unknown))
expect{ try self.requestLogin(str) }.to(throwError(errorType: RequestError.self))
func printStr(str: String) throws {
if str.isEmpty {
throw NSError(domain: "the value is empty", code: -1, userInfo: nil)
} else {
print(str)
}
}
func requestLogin(name: String) throws {
if name.isEmpty {
throw RequestError.Unknown
}
// request processing...
}
enum RequestError: ErrorType {
case Unknown
}
Collection Membership
関数 | 意味 |
---|---|
contain | actualにexpectの要素が含まれていた場合はテストが通ります。 |
beEmpty | actualが空だった場合にテストが通ります。 |
beginWith | actualの要素の最初がexpectの要素だった場合はテストが通ります。 |
endWith | actualの要素の最後がexpectの要素だった場合はテストが通ります。 |
let arr:[String] = ["Swift", "Objective-C", "Python"]
expect(arr).to(contain("Swift", "Objective-C"))
expect(arr).to(beginWith("Swift"))
expect(arr).to(endWith("Python"))
let emptyArr:[String] = []
expect(emptyArr).to(beEmpty())
Strings
関数 | 意味 |
---|---|
contain | actualにexpectの文字列が含まれていた場合はテストが通ります。 |
beginWith | actualがexpectの文字列から始まっていた場合にテストが通ります。 |
endWith | actualがexpectの文字列で終わった場合はテストが通ります。 |
match | actualの文字列がexpectの正規表現に合致している場合はテストが通ります。 |
let str = "My favorite language is Swift."
expect(str).to(contain("Swift"))
expect(str).to(beginWith("My"))
expect(str).to(endWith("."))
expect(str).to(match("My*"))
let emptyStr = ""
expect(emptyStr).to(beEmpty())
Checking if all elements of a collection pass a condition
ArrayやSetなどのSequenceTypeの場合、allPass
を用いてその中の要素全てが一定の条件を満たしているかどうかを調べることができます。
let arr = [1, 3, 5, 7]
let set = Set<Int>(arrayLiteral: 1, 3, 5, 7)
expect(arr).to(allPass({$0 < 10}))
expect(arr).to(allPass(beLessThan(10)))
expect(set).to(allPass({$0 < 10}))
expect(set).to(allPass(beLessThan(10)))
Verify collection count
ArrayやSet、DictionaryなどのCollectionTypeの場合、haveCount
を用いて要素数に関するテストを行うことができます。
let arr = [1, 3, 5, 7]
let set = Set<Int>(arrayLiteral: 1, 3, 5, 7)
expect(arr).to(haveCount(4))
expect(set).notTo(haveCount(5))