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.3k
通信のスタブ化を自作した話 (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
660
Better use of SwiftUI
tattn
0
450
Swift Concurrencyによる安全で快適な非同期処理
tattn
2
1.3k
iOSアプリの技術選択2022
tattn
7
3.9k
Widget Suggestions 対応と ヤフーの新OS対応
tattn
1
1.3k
WidgetKitで良い体験を作るには / Good experience with WidgetKit
tattn
2
1.7k
既存アプリにSwiftUIをどう組み込んでいくか
tattn
8
2.4k
iOS 14からのアプリ内課金
tattn
5
2.9k
iOS 14の位置情報系アップデート
tattn
0
22k
Other Decks in Technology
See All in Technology
カンファレンスに託児サポートがあるということ / Having Childcare Support at Conferences
nobu09
0
120
生成AIとM5Stack / M5 Japan Tour 2025 Autumn 東京
you
PRO
0
230
許しとアジャイル
jnuank
1
130
Why React!?? Next.jsそしてReactを改めてイチから選ぶ
ypresto
10
4.5k
20250929_QaaS_vol20
mura_shin
0
130
【新卒研修資料】LLM・生成AI研修 / Large Language Model・Generative AI
brainpadpr
25
17k
小学4年生夏休みの自由研究「ぼくと Copilot エージェント」
taichinakamura
0
500
オープンソースでどこまでできる?フォーマル検証チャレンジ
msyksphinz
0
100
研究開発部メンバーの働き⽅ / Sansan R&D Profile
sansan33
PRO
3
20k
ZOZOのAI活用実践〜社内基盤からサービス応用まで〜
zozotech
PRO
0
200
20201008_ファインディ_品質意識を育てる役目は人かAIか___2_.pdf
findy_eventslides
1
520
Function calling機能をPLaMo2に実装するには / PFN LLMセミナー
pfn
PRO
0
970
Featured
See All Featured
It's Worth the Effort
3n
187
28k
Automating Front-end Workflow
addyosmani
1371
200k
Designing for humans not robots
tammielis
254
26k
Making Projects Easy
brettharned
119
6.4k
Producing Creativity
orderedlist
PRO
347
40k
Intergalactic Javascript Robots from Outer Space
tanoku
273
27k
Context Engineering - Making Every Token Count
addyosmani
5
200
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
132
19k
Fantastic passwords and where to find them - at NoRuKo
philnash
52
3.4k
Making the Leap to Tech Lead
cromwellryan
135
9.5k
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
36
2.5k
What's in a price? How to price your products and services
michaelherold
246
12k
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