yaakaito.org

iOSアプリのテストを1週してみて思ったこと

Frank, GHUnit, Kiwi, Objective-C, SenTestingKit, Specta, Testing, iOS

こんにちは!うきょーです! iOSアプリのテストのことをそろそろ1年くらい考えていて、1周した感じもするので、 ここら辺で一旦の区切りの意味でもなんとなく考えをまとめてみる。 ちなみにテストというのは主に単体テストにフォーカスした内容です。

こういう系のエントリを書くと、僕はわりと誤解を生みやすい書き方をしてしまうので先に断っておくと、

  • なんらかのアプリ開発手法や、テスト手法をDisっているわけではないです。
  • フレームワークがいろいろ登場したりしますが、それらをDisっている訳ではないですし、それぞれ素晴らしいものだと思っています。
  • 同じくそのフレームワークを使っているプロジェクトも登場しますが、それらをDisっている訳でもありません。
  • もちろん特定個人をDisる内容でもありません。

という感じで、何かをDisってる記事ではないので、ご了承ください。

長めです。結局何がよかったの、っていう人は下の方から見てください。

テストフレームワークの変遷

はじめの一歩

皆さんご存知SenTestingKitです。 ほかにどんなフレームワークあるのか知らんし、とりあえずこれで書けなきゃどっちにしろ無理だろ、という感じでした。セットでOCMockも使っていました。 記憶が正しければ、Xcodeは4.1で、iOS4.x~を対象にしたアプリを作っていた記憶です。 ぶっちゃけこのころはSenTestingKitがどうしようもない感じで、状況によっては STAssertTrue(YES) が落ちたりしていました。 それでもある程度のテストを書くことはできました。モデルのテストは十分に書ける感じ。ビュー関連に関しては完全に諦めモードでしたが・・・。 この頃はアプリ全体をテストしていたのと、あんまり慣れていないところもあって、テストケースが冗長だったり、テストが思ったのと違う動きをしたり、いろいろありました。

安定を求めてGHUnitへ

まじなんとかならんの、とか思っていたときに良さげだなーとなったのがGHUnit。 GHUnitはそれ自体がアプリとして動いて、その上でテストコードを実行するというもの。 SenTestingKitと比較して圧倒的に安定しているのと、SenTestingKitと互換性があるのがプラス点。 さらにはGHVerifyViewというビューを画像の一致率でテストできるものもあったり、非同期テスト標準であったりと、盛りだくさん。 この時期に作っていたもので公開しているものでは、NLTHTTPStubServerはテストがGHUnitで書かれています。 アプリ開発をしつつも、アプリのコア部分をフレームワーク化する、ということもやっていたし、 特にフレームワーク側はちゃんとテスト固めないとなぁという意味でも、安定しているGHUnitを選択しました。 アサーションもSenTestingKitよりも断然豊富で、iOSのテストに慣れてきたのもあって、 テストケースはわりと綺麗に書けるようになっていった気がします。(どうしようもないところもありますが。)

使いはじめの時点ではほとんど問題なく、これでいいや、という感じでしたが、GHUnitは使い続けるうちに問題がでてきました。 GHUnitの問題点は、GHUnit自体がiOSアプリとして動くものなので、ターゲットを切り替えたりでどうしてもテスト実行とかがスムーズにできないところ。 GHUnitを2つのチームで使ってみましたが、保守できたのは片方だけだったので、思いの他このコストは高かったみたいです。 実行されないテストに価値はないし、実行時に障害があるのは問題外。同じアプリで動くタイプでBDDスタイルなCedarもこの段階で却下。 あとはテストログの表示がイケてないなーと思うところもありました。この辺りはちょっと弄ってあげればすむ話ではありますが、それでもコストが高め。

BDD系への転向

次に触ってみたのはObjective-C版RspecことKiwiです。 GHUnitの時と同じくこの時期に作っていたNLTQuickCheckにはテストにKiwiが使われています。 これはこの段階ではアプリのテストにはまったく利用していなくて、NLTQuickCheckみたいなObjective-C製のライブラリ開発に利用していました。 ライブラリを書いている分にはKiwiはめちゃくちゃいいです。QuickCheckみたいに、ビューが絡まない中で一旦非同期処理などもあまり考えずに済むというのは、テストを書いていく上では快適でした。 ただKiwiはすぐに問題にぶちあたることになります。単純にGHUnitのGHVerifyViewがアプリ開発で便利すぎたので、 どうしてもアプリを作ろうと考えるとGHUnitの方が良さそうに思えました。

ビューテストをするためにGHUnitへ戻る

そしてGHUnitへ戻ることになります。 結構複雑なビューをもつアプリを作っていたこともあって、ビューをコンポーネント単位で視覚的に動作確認できるのは、ものすごく便利でした。 ただやっぱりXUnit形式で書くのがもはやしんどい感じになっていて、ダルぽよ〜〜〜という状態でした。

もう一度BDD系へ戻る

Kiwiに戻ってきました。このあたりできれいなBDD、に対する回答を求めてTheRSpecBookなんかを読み始めました。 TheRSpecBookは非常によい本で、BDDってなんじゃ、みたいなところから実際にこんな感じで進めるといいよ、ということが書いてあって参考になりました。 ただ、じゃあそれiOSアプリ開発でObjective-C使って実践できんの、といわれるとそういう訳ではなく。 最初の方はとにかくSpecを全部書ききってから実装する、みたいなことも試してみましたが、あんまりしっくりこない。(ノッてるとうまくいっていると錯覚はする。) BDDやるならビヘイビアにドリブンなデベロップしないと意味ないよねー、後付けのBDDとか何の意味があるんや!!!ってことで、OH!やっぱり今回も駄目だったよ。状態に。

さらにBDD系を突き詰めてみる

けどここで引き下がるのも勿体ないので、注目度があがってきたSpectaに乗り換えてもう一度チャレンジしてみることに。 theValueを書かなくともよくなった分、やりやすさはあがった気がします。ヤッホーイ! だいたいモデル層はめちゃくちゃ綺麗にいけるようになってきたんですが・・・・え?View・・・?・・・ とりあえずビューはFrankでカバーするかーという感じになったのですが、結局のところ作ってみてだいたいこんな感じやろ、 という調整が多くなるビューでいちいちテストとか書いてられるか!!!という感じになった。

原点回帰

という訳でSenTestingKitまで戻ってきました。KiwiやSpectaが普段の開発で困らない分には動くってことは生でも動くってことです。 僕はあんまり思ってなかったんですが、Blocks使うとスタックトレースが追いにくくなるとかも、ちょいちょい聞くので素直にSenTestingKitがよくね?ということに。 それでもアサーションとかは不足しているので、OCHamcrestとかで補いつつ、非同期テストケースとかもサンプルがたくさんあるし、Githubなんかで拾ってこれば十分かなぁと。 普段はTDDで開発して、ビューは先に動きのプロトタイプを作ってしまって、あとからそれを補強していく、というので全然問題ないと思う。 GHUnitはやっぱり実行コストが高いし、SenTestingKitでいけるならそれでいいじゃない。Cmd+Uがないとテスト書く気起きないっていうか・・・。

変遷まとめ

とりあえずまとめておくと僕はこんな感じで使ってきました。 SenTestingKit -> GHUnit -> Kiwi -> GHUnit -> Kiwi -> Specta -> SenTestingKit

iOS開発でよくあることを踏まえたい

ここから割とエンジニアリングとは逆方向な内容になります。 ただ僕もエンジニアなので、ディレクターとかデザイナー目線でみると違うかもしれません。

ビューを優先して組み立てるべき

お客さんがみたいのはビューですし、ディレクターがみたいのもビューなら、みなさんがアプリ作りたいと思ったとき最初に頭に浮かぶのもビューです。 iOSアプリ作ってていつも思うのが、いくらモデル層をきれいに分離して、きれいなAPIを設計で綺麗なテストができたとしても、 アプリのファーストビューを表示するためにAPIを3つも4つも呼んでその上計算までしなきゃいけない、 とかだとハイパフォーマンスなアプリとは到底言えないですよね。ということ。 「本当の気持ちなんて伝えられるわけないのよ、だって私は、綺麗な世界とは違う世界をいきているんだもの!!!RESTfullとかしらないわよ!!!全部まとめて返してよ!!!」 という感じです。別にiOS限った話ではないですが、クライアントはだいたいそんな感じだと思ってます。(しつこいですがDisったりしてる意図ないです)

やっぱさ、新しい機能とか試してみたいじゃん?

ですよね、僕もそう思います。そういうのってスピードが大事じゃないですか。 テスト書いてる暇とかないわ、というのは嘘かサボりだと思いますが、プロトにいちいちテスト書いててもキリがないというのはあると思います。 OS自体のアップデートも早いし、界隈の流行り廃りもまだまだ早いので、保守にあまりにもコストを掛けすぎるのはあまりチャレンジングではないと思います。

なんか思ってたのと違うんだけど

よくあることです。諦めてください。 テスト書きまくって苦労しました、みたいなアホな状態になってたら悪いのはちゃんとプロト見せたりしなかった開発者です。

テストってさ?コストかかるんでしょ?

開発をサポートするテストは書いても書かなくても開発速度にそこまで差はないと思います。 長期的にみたらテストは書いた方がいいですが。

人海戦術でなんとかなるっしょ?

アホか。

と言いたいところですが、そういう場面があることは確かです。

よくあることまとめ

まあ、iOS開発に限ったことじゃないですが(2回目ですね)、巻き戻しとか、もっといいの目指そう!とかそういうのはあるし、 それが正しい場面もあるので、あんまり過剰にやりすぎないようにしないとトータルとしてのパフォーマンスが下がるので、それでは意味がない。

とりあえず1週目の結論として

普通にアプリ作る分には単体テストはSenTestingKit+拡張マッチャでモデル中心に大丈夫と思えるところまで書け。ということになりました。割と普通です。 カバレッジを100%にあげるとかはかなりしんどいですし、多分リターンは思ったより少ないです。iOSアプリ、試作とかでわりと中変えたりすること多いし。 なのであるリリースラインに向けて、開発者(とディレクター)がよし行ける!と思える分のテストが書ければそれでよいと思います。 もちろん高いことに超したことはないです。例えば何かのSDK作るとかって場面ではカバレッジは意識した方が良いに決まっています。 けれど、無理にカバレッジをあげるよりも、大丈夫!というラインで意識する方が、いろいろ進めやすいと思います。 勘違いのないように言っておきますが、テスト書かなくともいいやろ、という事ではありません。 テストを書くのが目的にならないように、ということです。本質はそこではありません。 設計上テストが楽になるライブラリとかもあればガンガン使えばいいと思います。(CoreDataで言えばMagicalRecodeとか)

そして、いわゆる単体テストでビュー全体の遷移とかをカバーしようとすると相当しんどいです。 このあたりにはFrankやKIFなどのドライバー系のツールをうまく使うとよいと思います。 TheRSpecBookなんかにも出てくる二重のテストイテレーションを真似ましょう。 ただFrankやKIFについてはまだなんか話せるほど知識がないので2週目にご期待くださいということで。

普通に、と言ったのにはちょっと意味があって、テストフレームワークに関して言えば、アプリによって変えるべきです。 ここでいう普通のアプリは例えばユーティリティアプリだったり、何かのサービスのクライアントだったり、という感じです。 iOSでガッツリ動くゲームとか、もっと大規模なものを作りたいなら、いまのところはまだGHUnitの方がよいと思います。 逆にビューの存在しないライブラリを書くのであればBDD系のフレームワークでBDDしても全然OKだと思います。

まとめ

こんなこと書いてて僕が言うのもあれなんですが、テスト書くの目的にしちゃうと絶対に終わらないです。 ここのテストしんどそうだなーと思ったら、それはしんどいんだと思います。 時には人海戦術でパターン入れてみる、というのも必要だと思いますし、 結局のところ人が触らないと分からないところも多いですが、それもテストです。