Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
通信のスタブ化を自作した話 (iOS)
Search
Tatsuya Tanaka
January 14, 2017
Technology
1
1.2k
通信のスタブ化を自作した話 (iOS)
iOSでのユニットテスト用に通信をスタブ化する方法とその実装方法について。
@iOS_LT #25
Tatsuya Tanaka
January 14, 2017
Tweet
Share
More Decks by Tatsuya Tanaka
See All by Tatsuya Tanaka
iPhoneのセンサー情報をmacOSアプリでリアルタイム活用するための技術
tattn
1
510
Better use of SwiftUI
tattn
0
380
Swift Concurrencyによる安全で快適な非同期処理
tattn
2
1.1k
iOSアプリの技術選択2022
tattn
7
3.7k
Widget Suggestions 対応と ヤフーの新OS対応
tattn
1
1.2k
WidgetKitで良い体験を作るには / Good experience with WidgetKit
tattn
2
1.5k
既存アプリにSwiftUIをどう組み込んでいくか
tattn
8
2.3k
iOS 14からのアプリ内課金
tattn
5
2.7k
iOS 14の位置情報系アップデート
tattn
0
22k
Other Decks in Technology
See All in Technology
SREによる隣接領域への越境とその先の信頼性
shonansurvivors
2
520
ハイパーパラメータチューニングって何をしているの
toridori_dev
0
140
Adopting Jetpack Compose in Your Existing Project - GDG DevFest Bangkok 2024
akexorcist
0
110
リンクアンドモチベーション ソフトウェアエンジニア向け紹介資料 / Introduction to Link and Motivation for Software Engineers
lmi
4
300k
B2B SaaSから見た最近のC#/.NETの進化
sansantech
PRO
0
750
オープンソースAIとは何か? --「オープンソースAIの定義 v1.0」詳細解説
shujisado
7
810
TypeScript、上達の瞬間
sadnessojisan
46
13k
スクラムチームを立ち上げる〜チーム開発で得られたもの・得られなかったもの〜
ohnoeight
2
350
隣接領域をBeyondするFinatextのエンジニア組織設計 / beyond-engineering-areas
stajima
1
270
DMARC 対応の話 - MIXI CTO オフィスアワー #04
bbqallstars
1
160
AWS Lambdaと歩んだ“サーバーレス”と今後 #lambda_10years
yoshidashingo
1
170
スクラム成熟度セルフチェックツールを作って得た学びとその活用法
coincheck_recruit
1
140
Featured
See All Featured
Build The Right Thing And Hit Your Dates
maggiecrowley
33
2.4k
Fantastic passwords and where to find them - at NoRuKo
philnash
50
2.9k
The Pragmatic Product Professional
lauravandoore
31
6.3k
How STYLIGHT went responsive
nonsquared
95
5.2k
Dealing with People You Can't Stand - Big Design 2015
cassininazir
364
24k
Java REST API Framework Comparison - PWX 2021
mraible
PRO
28
8.2k
Teambox: Starting and Learning
jrom
133
8.8k
Git: the NoSQL Database
bkeepers
PRO
427
64k
Bash Introduction
62gerente
608
210k
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
27
840
The Language of Interfaces
destraynor
154
24k
Thoughts on Productivity
jonyablonski
67
4.3k
Transcript
通信の スタブ化 を自作した話 Presented by Tatsuya Tanaka tattn tattn tanakasan2525
通信部分のユニットテスト 実際に通信をしてテストすることも可能。 しかし、 スタブ化することで以下のような メリット がある。 無駄なサー バー 負荷を減らせる サー
バー が開発中・ 停止中でもテストできる 任意の通信エラー をテストできる 2
通信部分のスタブ化 iOS では3 つの方法がよく用いられている。 1. 依存性注入 (DI) 2. 継承 3.
ライブラリ (OHHTTPStubs, OCMock, Mockingjay) 3
1. 依存性注入 (DI) class APIClient { let requester: Requestable init(requester:
Requestable = RealRequester()) { self.requester = requester } ... } class RealRequester: Requestable {} // 実際の通信用 class MockRequester: Requestable {} // モック用 APIClient(requester: MockRequester()) // モックに差し替え 予めDI を意識した設計が必要 4
2. 継承 open class APIClient { open func request() {}
} class MockAPIClient: APIClient { override func request() {} // モックに上書き } let apiClient: APIClient = MockAPIClient() 継承できないstruct では不可 クラスごとにモック用のクラスを作るのが面倒 5
3. ライブラリ (OHHTTPStubs, OCMock, Mockingjay) OHHTTPStubs で、 レスポンスをJSON に差し替える場合 stub(isHost("mywebservice.com"))
{ _ in let stubPath = OHPathForFile("wsresponse.json", type(of: self)) return fixture(filePath: stubPath!, headers: ["Content-Type":"application/json"]) } 使いやすい 6
今回は3 のように扱える仕組みを実装 7
その方法とは... 8
Method Swizzling & URLProtocol 9
Method Swizzling メソッドの実装を差し替える手法。 Objective-C のランタイムを使用する。 紹介時間が足りないので 詳しくはアヒルの方の記事を見てね http://qiita.com/paming/items/25eaf89e4f448ab05752 注意: Pure
なSwift のクラスでは利用できない (Swift のリフレクションは読み込み専用のため) 10
URLProtocol サポー ト外のプロトコルで通信する場合や リクエストを独自の方法で処理する時に使うクラス。 継承して独自の処理を実装。 少なくとも4 つのメソッドをoverride が必要。 class func
canInit(with request: URLRequest) -> Bool class func canonicalRequest(for request: URLRequest) -> URLRequest func startLoading() func stopLoading() 11
class func canInit(with request: URLRequest) -> Bool 引数のリクエストが処理できるか否かで真偽値を返す func startLoading()
独自のリクエストの処理を実装する func stopLoading() リクエストのキャンセル時の処理を実装する 12
スタブ化できそうな気が してきますね 13
モック用URLProtocol の実装 public class MockURLProtocol: URLProtocol { override open class
func canInit(with request: URLRequest) -> Bool { return true } override open class func canonicalRequest(for request: URLRequest) -> URLRequest { return request } override open func startLoading() { // 次のスライドで実装 } override open func stopLoading() { } } 14
モックデー タを返す override open func startLoading() { let delay: Double
= 1.0 // 通信に1 秒かかるモック DispatchQueue.main.asyncAfter(deadline: .now() + delay) { let json: [String: Any] = ["mock": "data"] let data = try! JSONSerialization.data(withJSONObject: json, options: .prettyPrinted) self.client?.urlProtocol(self, didLoad: data!) self.client?.urlProtocolDidFinishLoading(self) } } client はURLProtocol が持っているURLProtocolClient プロトコル。 これを利用して結果を返す。 15
作ったプロトコルを使ってみる プロトコルを登録 URLProtocol.registerClass(MockURLProtocol.self) URLSessionCon guration にもプロトコルを登録 let configuration = URLSessionConfiguration.default
configuration.protocolClasses?.insert(MockURLProtocol.self, at: 0) 16
作ったプロトコルを使ってみる 作ったcon guration を利用して通信 let url = URL(string: " テストするAPI
のURL")! URLSession(configuration: configuration).dataTask(with: url) { data, _, _ in let json = try! JSONSerialization.jsonObject(with: data!, options: .allowFragments) print(json) }.resume() json にはモックデー タの["mock": "data"] が入っている。 ( 通信はしていないので" テストするAPI のURL" のまま動かしてもこの結果) 17
18
ですが 19
独自のプロトコルの作成 public class MockURLProtocol: URLProtocol { ... } プロトコルを登録 URLProtocol.registerClass(MockURLProtocol.self)
URLSessionCon guration にプロトコルを登録 let configuration = URLSessionConfiguration.default configuration.protocolClasses?.insert(MockURLProtocol.self, at: 0) con guration をURLSession に渡す URLSession(configuration: configuration) 20
面倒 21
そこで Method Swizzling 22
URLSessionCon guration の実装を置換 public extension URLSessionConfiguration { public class func
setupMock() { let `default` = class_getClassMethod(URLSessionConfiguration.self, #selector(getter: URLSessionConfiguration.default)) let swizzled = class_getClassMethod(URLSessionConfiguration.self, #selector(getter: URLSessionConfiguration.mock)) method_exchangeImplementations(`default`, swizzled) } private dynamic class var mock: URLSessionConfiguration { let configuration = self.mock configuration.protocolClasses?.insert(MockURLProtocol.self, at: 0) URLProtocol.registerClass(MockURLProtocol.self) return configuration } } 23
すると、 このようになります 24
// モックに置き換え URLSessionConfiguration.setupMock() let url = URL(string: " テストするAPI のURL")!
URLSession.shared.dataTask(with: url) { data, _, _ in let json = try! JSONSerialization.jsonObject(with: data!, options: .allowFragments) print(json) }.resume() It's シンプル ( URLSession.shared でも使えます) 25
もっと使いやすくしたい 26
ライブラリ化しました ( 宣伝) https://github.com/tattn/Mokei Method Chain でレスポンスを自由にカスタマイズ stub.url("example.com") .json(["test": "data"])
.delay(0.5) // wait for 500ms .statusCode(400) Bundle 内のJSON ファイルをスタブとして登録可能 stub.json(filename: "fixture") // fixture.json Quick/Alamo re でも使えます 27
車輪の再発明楽しい 28
参考 Alamo re, URLSession の通信処理をMethod Swizzling でスタブに置き 換える http://qiita.com/tattn/items/e7db12f84fa51b3631d2 Swift
における現実的なモック https://realm.io/jp/news/tryswift-veronica-ray-real-world-mocking- swift/ Using NSURLProtocol for Testing https://yahooeng.tumblr.com/post/141143817861/using- nsurlprotocol-for-testing 29