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
550
Better use of SwiftUI
tattn
0
410
Swift Concurrencyによる安全で快適な非同期処理
tattn
2
1.2k
iOSアプリの技術選択2022
tattn
7
3.8k
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.8k
iOS 14の位置情報系アップデート
tattn
0
22k
Other Decks in Technology
See All in Technology
フォーイット_エンジニア向け会社紹介資料_Forit_Company_Profile.pdf
forit_tech
1
1.7k
サイト信頼性エンジニアリングとAmazon Web Services / SRE and AWS
ymotongpoo
7
1.7k
実は強い 非ViTな画像認識モデル
tattaka
3
1.3k
データベースの負荷を紐解く/untangle-the-database-load
emiki
2
530
わたしがEMとして入社した「最初の100日」の過ごし方 / EMConfJp2025
daiksy
14
5.2k
脳波を用いた嗜好マッチングシステム
hokkey621
0
290
自分だけの仮想クラスタを高速かつ効率的に作る kubefork
donkomura
0
110
AWSを活用したIoTにおけるセキュリティ対策のご紹介
kwskyk
0
400
困難を「一般解」で解く
fujiwara3
7
1.3k
AWS Well-Architected Frameworkで学ぶAmazon ECSのセキュリティ対策
umekou
2
150
AIエージェント開発のノウハウと課題
pharma_x_tech
5
3.8k
いまからでも遅くない!コンテナでWebアプリを動かしてみよう!コンテナハンズオン編
nomu
0
170
Featured
See All Featured
Building Applications with DynamoDB
mza
93
6.2k
The MySQL Ecosystem @ GitHub 2015
samlambert
250
12k
Into the Great Unknown - MozCon
thekraken
35
1.6k
XXLCSS - How to scale CSS and keep your sanity
sugarenia
248
1.3M
GitHub's CSS Performance
jonrohan
1030
460k
Imperfection Machines: The Place of Print at Facebook
scottboms
267
13k
RailsConf & Balkan Ruby 2019: The Past, Present, and Future of Rails at GitHub
eileencodes
133
33k
Designing for Performance
lara
604
68k
Raft: Consensus for Rubyists
vanstee
137
6.8k
How to Create Impact in a Changing Tech Landscape [PerfNow 2023]
tammyeverts
49
2.3k
Designing Experiences People Love
moore
140
23k
Exploring the Power of Turbo Streams & Action Cable | RailsConf2023
kevinliebholz
30
4.6k
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