そもそものユニットテストって?
プログラムを構成する比較的小さな単位(ユニット)が個々の機能を正しく果たしているかどうかを検証するテストです。通常、関数やメソッドが単体テストの単位(ユニット)となります。
今のところなんとなくメソッド、クラスが実装した通りに動くことを証明するもの。というイメー
ジ。iOSではメソッドが実装通り動いても、振る舞いが予想通りいかないことが往々にしてあるので、これだけやっておけばいいわけじゃない。
書くことのメリット
これにはいろいろな意見があった。
- コードの変更を許容しやすくなる
- コードの理解を助ける
- 実装者の意図しない変更を防ぐ
- コードを書くことを意識して書くと、ほどよくメソッドがほぐれる
確かに振り返ってみても、コードを読んでて「ここどんな値が来るんだろうな〜。せや、ブレークポイント使ってウォッチしよ!」とかってことよくあった。Xcodeならまだしも、PHPとかRubyとかスクリプト系の言語だとデバッグするのもめんどくさい。
テストコードがあって、入出力系のテストコードがあれば理解の助けになるなとも思った。
ふむふむ。
*参考
[ユニットテストを書こう!]
(http://qiita.com/kompiro/items/78f2c8d2022a685baa83)
ユニットテストはなぜ必要なの? (1/2)
よくある間違い
- ユニットテストを導入するならば、実装コストの2倍以上を見積もるべきです。
- ダメなテストコードは結局品質も担保できない
- ちゃんと継続的に使和なければ意味がない
2倍以上。たしかに。頑張って書いてみたら大変だった。
テストコードを書いているとプロダクションコードも直さなければってなったりもするし、
最初からちゃんとテストを意識して実装していくことが非常に大事。
iOS(Xcode)におけるユニットテスト
[Xcodeユニットテストガイド]
(https://developer.apple.com/jp/documentation/UnitTesting.pdf)をちょっと読んでみたが古い。
Xcode4時代のメソッドで書かれている。STAssertなんたらというのは古いようだ。
Xcode5からはXCTestというのが使える。プロジェクトにも初めからtestが履いていてCmd+Shift+Uを押すだけで動いてくれる。
XCTest
import UIKit
import XCTest
class UnitTestTests: XCTestCase {
override func setUp() {
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
}
func testExample() {
// This is an example of a functional test case.
XCTAssert(true, "Pass")
}
func testPerformanceExample() {
// This is an example of a performance test case.
self.measureBlock() {
// Put the code you want to measure the time of here.
}
}
}
このようなテストコードが最初から用意される。
ボタン押すだけでテスト単位で実行されるし、success/failのログが横にポチッと残っているのでわかりやすい。
Testコードの書き方
PHPUnitもJUnitもなんかしらのユニットテストクラスを継承して、test*というプレフィックスで関数を用意すればいい。
PHPUnit
XCTestは歴史が浅いので、テストの例とかはPHPの方がいっぱいあって勉強になるかも・・・
テストコードを読む
PHPのほうがええんやないか〜と思っていたが、信頼あるiOSのライブラリたちはちゃんとテストコードが書かれている。
####AFNetworking
AFNetworkingはしっかり書いてあった。メソッド名がtestThatAFHTTPRequestSerialiationSerializesQueryParametersCorrectlyとかアンチObjCの人にまたディスられそうなメソッドが散見されるが・・・
https://github.com/AFNetworking/AFNetworking/blob/master/Tests/Tests/AFHTTPRequestSerializationTests.m#L68
####BlocksKit
ラップされたBlocksメソッドを提供するBlocksKitはメタプログラミング的なテストコードを書いていた。
https://github.com/zwaldowski/BlocksKit/blob/master/Tests/A2BlockInvocationTests.m#L116
####SDWebImage
SDWebImageをみていたら見慣れないメソッドがあった
https://github.com/rs/SDWebImage/blob/master/Tests/Tests/SDImageCacheTests.m#L50
expect([self.sharedImageCache diskImageExistsWithKey:kImageTestKey]).to.equal(NO);
なんだろうと調べたらexpectaというのを使っていた。
ユニットテストにもライブラリはいろいろあるようだ。
iOSにおけるテストコードのライブラリ
Expecta
ExpectaはGithubのメンバーが作ったマッチャーです。BDDフレームワークであるSpecta(これもGithub製)と組み合わせて使用すると、手軽にテストコードが書けます。特に非同期テストの書きやすさは秀逸です。是非皆さんも一度使ってみることをお勧めします。
specta
http://qiita.com/yuki_okawa/items/1586375581ea8cc510fa
Cocoapodsでも推奨されているそうだ。
matcherにはexpectaを使っている。
そもそもマッチャーってなんだろうな
JUnitには以下のようなものがある。
登場するマッチャー
・等価であるか:is()
・等価でないか:not()
・nullであるか:nullValue()
・nullでないか:notNullValue()
・指定したインスタンスか:instanceOf()
・同じインスタンスか:sameInstance()
・全ての条件を満たすか:allOf()
・いずれかの条件を満たすか:anyOf()
・値は何でもよい:anything()
・トレースに表示する文字列を変更:describedAs()
OCMock
http://safx-dev.blogspot.jp/2012/03/ocmock.html
http://ocmock.org/
そもそもモックとは?
外部APIとかStrutsのDynaActionFormとか、単純にインスタンス化できないものをMock化してテストできる
Javaの単体試験におけるモックの活用メモ EasyMock、PowerMock
ということす。利用方法としては以下のように仮想的にインスタンスを作ってレスポンスのモックを作れる。
// Mock生成
id mock = [OCMockObject mockForClass:[SampleClass class]];
[[[mock stub] andReturn:True] someMethod:@"FOOBAR"];
// Test
XCTAssert([mock someMethod:@"FOOBAR"], @"OK");
こうやってみるとめちゃんこ便利だ。
まえまでこのロジックはプロダクションコードに埋め込んでいたこともあったけど、全部テストコード側にもっていける。もちろん、APIであればレスポンスがちゃんとこのモックと一致している必要もある。
そこもテストコードでカバーすればいい。
*参考
Objective-Cのテストでのモックとかスタブとか差し替えの話
テスト取り巻く考え方
継続的インテグレーショ・・・うう、頭痛が。
知ってはいるつもりだし、事例もよく聞くし、いろいろなツールがあることもしているけど、
iOSって視点ではどうなんだろう。
まとめ
iOSはユニットテストの仕組みがすでにあるし、ライブラリもある。
それをつかったコードもOSSを見れば勉強になるし、導入はしやすい。
継続的に回すための仕組みはTravisCIやJenkinsがカバーしてくれる、
あとは有能なエンジニアがテストコードを書ければ品質をあげることができるはず。
というのは机上の話で、「Modelだけ一応書いている」とか「書いていたけど最近動いていない」とかっていうのもよく聞く。
ただテストを意識して書くことっていうのは本当に大事だし、仕様が複雑になるので最低限はテストコードでカバーしようっていうのを頭の片隅において胃置くだけでも全然違うんじゃないかなと思った。
いい休日になった。