OHHTTPStubsを使ったiOSアプリ開発の進め方


# 概要

スタートアップiOS勉強会 #3
http://www.zusaar.com/event/4557003

自分もこれで話す内容についてずっと考えていて、laisoさんの[スタートアップの人材戦略とは何か](http://laiso.hatenablog.com/entry/2014/03/27/%E3%82%B9%E3%82%BF%E3%83%BC%E3%83%88%E3%82%A2%E3%83%83%E3%83%97%E3%81%AE%E4%BA%BA%E6%9D%90%E7%8D%B2%E5%BE%97%E6%88%A6%E7%95%A5%E3%81%A8%E3%81%AF%E4%BD%95%E3%81%8B)を読むと先にLTの内容を公開していたのでこちらも公開することにした。

10分以下のLTだと細かいことは多分話せないので、先におおまかに公開し実際のLTでは自分の特に話したいことを集中して話すようになるのではないかと思う（もしくは[ジョブズが成仏した話みたいに](http://qiita.com/yimajo/items/336fd0039bd9d35887f6)当日思いつきでぜんぜん違うことを言うかもしれないし、もしくは近所にガールズバーができたから行ってみたらビール一杯3080円だった話になるかもしれない）。

## スタートアップに関する話について

経験上、iOSアプリ開発ではWebアプリ開発のようにいきなり大人数で開発を進めることがないので、iOSアプリ開発での技術的ノウハウやあるあるネタはスタートアップでの開発あるあるに近いものだという実感があって、スタートアップに関する話を無理にしようとせず、普通にiOSアプリ開発で使うライブラリの話はスタートアップでもきっと役に立つ。なので自分が開発の初期時によく利用する[OHHTTPStubs](https://github.com/AliSoftware/OHHTTPStubs)を使って進める開発についてがいいかなと思った。

# OHHTTPStubsについて

[OHHTTPStubs](https://github.com/AliSoftware/OHHTTPStubs)はiOSアプリのNSURLRequestやNSURLSessionの通信を内部でフックしてスタブ用のレスポンスを返すライブラリ。これを使うことによってAPIがサーバ側で実装されていなくてもクライアントアプリ開発者は開発を進めることが出来る。基本的にNSURLRequestやNSURLSessionの通信をフックしてくれるので、例えばAFNetworkingなどの通信ライブラリを使ったものでも利用できる。

## 具体的なイメージ

API開発者とクライアントアプリ開発者でAPI仕様を詰めた後、API開発者にレスポンスのjsonなどをテキストファイルとして作ってもらう。

この副次的なメリットとして、実際にjsonとしてパース出来るものにしてもらうことで、エクセルなどで作られた仕様書だとDictionaryやArrayの記述間違いがあったりjsonとして正しくない記述に対する些細な指摘ややりとりをする必要もなくなる。


# 使い方

## ライブラリの基本的な使い方

```objc
[OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request)
{
    return <#YESならwithStubResponse:のハンドラを実行#>;
} withStubResponse:^OHHTTPStubsResponse *(NSURLRequest *request) 
{
    return <#スタブ用のレスポンスを返す#>;
}];
```

最初のblocksでは全てのHTTP/HTTPSリクエストに対して特定のリクエストをフックするかどうかをBOOLで返す。


## 具体的な使い方

OHHTTPStubs#stubRequestsPassingTest:withStubResponse:メソッドは複数作成できるが、管理しないと全てのリクエストに対して実行されてしまうので、全てのリクエストが集中すること前提で条件分岐させるほうが分かりやすい。

```objc
[OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request)
{
    if ([request.URL.absoluteString isEqualToString:urlString1]){
       return YES;
    } else if ([request.URL.absoluteString isEqualToString:urlString2]){
       return YES;
    }
    return NO;
} withStubResponse:^OHHTTPStubsResponse *(NSURLRequest *request) 
{
    if ([request.URL.absoluteString isEqualToString:urlString1]){
        return 
        [OHHTTPStubsResponse responseWithFileAtPath:OHPathForFileInBundle(@"stub.txt",nil)
                                         statusCode:200
                                            headers:@{@"Content-Type":@"text/plain"}];

    }
    //...他の条件を続ける
}];
```

OHHTTPStubsのREADMEやサンプルコードを読んでもこのような使い方はしておらず、通信毎にこのメソッドを作っている。おそらくスタブをon/offしたい場合はそれでよいが、上記のようにやるほうが楽だし問題を感じたことはない。

コードを見てもらってわかるように、ステータスコードとレスポンスを指定することが出来る。なので上記のように200OKとせず認証エラーにしてみて異常系のテストを実行することも出来る。先に正常系、異常系の分岐を書いておきTODOとコメントしておくことで後々異常系の実装をする箇所が明確になるし、異常系の動作をどのようにするかはアプリによって違ってくるものなので先に設計上の指針を示すためにも良い。

## 専用のUtilityクラスなどを作ると捗る

DemoプロジェクトはViewControllerで通信してそこでOHHTTPStubsを使っているが、OHHTTPStubsの処理は1クラスにまとめてAppDelegateでスタブをon/offするほうが便利。


## 注意点

### リリースバイナリにこのライブラリは含めてはないけない

リリースするバイナリに含めないようにリリースビルドからlibOHHTTPStubs.aファイルを除外されるようにする。例えばテスト用のターゲットを作り、クラスのimport部分を#ifdefもしくは#ifマクロで囲うなどで実現する。

自分の経験上このような部分を作りこんではいけない。このような本質とは関係ない部分を作りこみ確実という自信があろうとも、絶対確認をしないといけないためにその手順が複雑になると確認手順に時間が掛かる。それは絶対によくない。複雑な確認手順は手順自体の正当性も気になってくるし、正当性を気にしない甘い人間は自分の都合の良い用に確認手順を捉えることもある。

チェックが必要な仕組みを作るときは、チェックしやすいかの優先度を高めて考えるべきで、たとえ下手な方法でもチェックがし易いなら時間コスト下げてくれるのでそちらの方がいい。

# ライブラリの変更点

## レスポンスを返すインターフェースが変わっていた

変更点

- headersのDictionaryでcontentTypeも返す
- レスポンスタイムは指定が必須ではなくなった
- レスポンスタイムは定数化されている
- リクエストまでにかかる時間も指定できるようになった

日本語のブログでググった限り、前はこうだった

```objc
[OHHTTPStubsResponse responseWithFile:@"response.json" 
                          contentType:@"text/json"
                         responseTime:2.0];
```

新しくなってるのはこのようになる

```objc
[[OHHTTPStubsResponse responseWithFileAtPath:OHPathForFileInBundle(@"stub.txt",nil)
                                  statusCode:200
                                     headers:@{@"Content-Type":@"text/plain"}]
 requestTime:1.f
 responseTime:OHHTTPStubsDownloadSpeedWifi];
```

# おわりに

スタートアップiOS勉強会 #3
http://www.zusaar.com/event/4557003

まだ定員に達してないので参加したい方は参加してみたらいいかもしれない。

ちなみに1回目のときはこんな感じでした

![BggcLLSCAAEKZTu.jpg](https://qiita-image-store.s3.amazonaws.com/0/16400/4acf4dbc-6d09-12c1-cc12-8a4373ee7d3f.jpeg "BggcLLSCAAEKZTu.jpg")