Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Async network logic testing using Protocol (Tes...
Search
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
Wooseong Kim
December 13, 2017
Programming
470
2
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Async network logic testing using Protocol (Test Stub)
presented at Swift Korea meetup (2017/12/13)
Wooseong Kim
December 13, 2017
More Decks by Wooseong Kim
See All by Wooseong Kim
Tuist, 도입은 했는데 그래서 Modular Architecture 는 어떻게 만들어 가나요?
innocarpe
0
690
Modular Architecture w/ Tuist
innocarpe
0
800
Test-Driven-Development with ReactorKit in StyleShare
innocarpe
1
1k
Developing iOS with Rx, MVVM
innocarpe
1
230
git + Pull Request + Code Review and Project Management with Agile
innocarpe
0
120
Online Board(Trello), Scrum(Pivotal Tracker), Kanban(JIRA Agile)
innocarpe
0
160
Android TV at Google Developers Summit
innocarpe
0
140
경북대도서관 스마트앱 최종발표
innocarpe
0
230
Other Decks in Programming
See All in Programming
新人はどこまで自力でやり、どこからAIに頼るべきか/エンジニア育成に向き合う_先輩たちの悩みと知見共有会
toppan_digital_dev
1
620
マイコン向けの軽量Ruby「PicoRuby」で各種デバイスを制御するネイティブアプリの実現手法
bash0c7
0
380
AI に Inclusive UI を書かせよう — Design Rules Skill で Compose UI を作り直す
theoriatec2024
1
490
What We Talk About When We Talk About XP
m_seki
2
610
高専キャリア LT 発表内容
crysta1221
6
5.7k
ハーネス設計入門 〜プロンプト、コンテキストの次〜
kinopeee
56
38k
個人開発基盤をまるごとCloudflareに引っ越して爆速で総合的体験を向上させた話
tinykitten
0
180
AIエージェント時代のコードレビューを設計する
nogu66
6
2.7k
技術的負債の返済は、AI時代の複利で効く投資 — 経営としての意思決定とその遂行
curekoshimizu
0
790
XP祭りでしか伝わらないフリップネタ #xpjug
murabayashi
0
150
JPUG勉強会 OSSデータベースの内部構造を理解しよう(第2回)
oga5
0
200
AgentCore CLI で進化した AWS での AI エージェントの作り方 : 必要な機能を必要な時に
icoxfog417
PRO
3
340
Featured
See All Featured
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
38
3k
Building Flexible Design Systems
yeseniaperezcruz
330
41k
AI: The stuff that nobody shows you
jnunemaker
PRO
9
1k
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
32
4.8k
Efficient Content Optimization with Google Search Console & Apps Script
katarinadahlin
PRO
1
850
The agentic SEO stack - context over prompts
schlessera
0
930
Crafting Experiences
bethany
1
330
CoffeeScript is Beautiful & I Never Want to Write Plain JavaScript Again
sstephenson
162
16k
The Art of Programming - Codeland 2020
erikaheidi
57
14k
The Illustrated Guide to Node.js - THAT Conference 2024
reverentgeek
1
480
Heart Work Chapter 1 - Part 1
lfama
PRO
9
36k
Scaling GitHub
holman
464
140k
Transcript
Wooseong Kim, StyleShare Async network logic testing using Protocol Session
2 #swiftkorea Testing
Problems in networking logic testing Concept - Test Stub Case
study (with Test Stub)
Problems in networking logic testing
None
Want to test here
Want to test here But it’s hard
Because of this!
What if… Offline? Network problem? Server issue? Auth issues?
What if… Offline? Network problem? Server issue? Auth issues?
What if… Offline? Network problem? Server issue? Auth issues?
What if… Offline? Network problem? Server issue? Auth issues?
Offline? Network problem? Server issue? Auth issues? What if…
If the network fails, tests will fail too. What if…
If the network fails, tests will fail too. What if…
Unhappy
Expected Real world →
Expected Solution →
Fake networking?
Fake Networking Request Response Request Response Same response for same
request.
Fake Networking Useful in test environment Production Testing
Called Test Stub Production Testing
So today’s real topic is,
Wooseong Kim, StyleShare #swiftkorea Testing Async network logic testing using
Test Stub Session 2
Case study (with Test Stub)
Case study - Log In
None
None
None
Log-In Button Tap ID/PW Response HTTP Request HTTP Response
Test Stub → ID/PW Response Testing environments Log-In Button Tap
Structures
Production vs Test
Production vs Test Different operation, but same interface
Production vs Test …so they can conform the same protocol!
Production vs Test Difference service on different environment
Codes
protocol AuthServiceType { func logIn(id: String, pw: String, ...) }
protocol AuthServiceType { func logIn(id: String, pw: String, ...) }
// Production class AuthService: AuthServiceType { func logIn(id: String, pw: String, ...) { // Networking logic } }
protocol AuthServiceType { func logIn(id: String, pw: String, ...) }
// Production class AuthService: AuthServiceType { func logIn(id: String, pw: String, ...) { // Networking logic } } // Test class StubAuthService: AuthServiceType { func logIn(id: String, pw: String, ...) { // Stub logic } }
// Production class LogInViewController: UIViewController { var authService: AuthServiceType! ...
} let viewController = LogInViewController() viewController.authService = AuthService() // Test let viewController = LogInViewController() viewController.authService = StubAuthService()
Writing test code with StubAuthService
// Should store the token after logged in class LogInViewController:
UIViewController { ... }
// Should store the token after logged in class LogInViewController:
UIViewController { var authService: AuthServiceType! func logInButtonDidTap() { authService.logIn(id: "id", pw: "pw", completion: { response in // Completion logic } } }
protocol AuthServiceType { func logIn(id: String, pw: String, completion: (Response)
-> Void) } // Production class AuthService: AuthServiceType { func logIn(id: String, pw: String, completion: (Response) -> Void) { // Networking logic } } // Test class StubAuthService: AuthServiceType { func logIn(id: String, pw: String, completion: (Response) -> Void) { // Stub logic } }
// Test class StubAuthService: AuthServiceType { func logIn(id: String, pw:
String, completion: (Response) -> Void) { let response = Response(token: "token") // Works synchronously completion(response) } }
// Test class StubAuthService: AuthServiceType { func logIn(id: String, pw:
String, completion: (Response) -> Void) { let response = Response(token: "token") completion(response) } } // Production class LogInViewController: UIViewController { func logInButtonDidTap() { authService.logIn(id: "id", pw: "pw", completion: { response in // Passed to here! } } }
func testLogIn() { ...? }
func testLogIn() { // given let viewController = LogInViewController() viewController.authService
= StubAuthService() }
func testLogIn() { // given let viewController = LogInViewController() viewController.authService
= StubAuthService() // when viewController.logInButtonDidTap() }
func testLogIn() { // given let viewController = LogInViewController() viewController.authService
= StubAuthService() // when viewController.logInButtonDidTap() // then XCTAssertEqual(viewController.token, "token") }
func testLogIn() { // given let viewController = LogInViewController() viewController.authService
= StubAuthService() // when viewController.logInButtonDidTap() // then XCTAssertEqual(viewController.token, "token") // Failed }
// Production class LogInViewController: UIViewController { var authService: AuthServiceType! var
token: String func logInButtonDidTap() { authService.logIn(id: "id", pw: "pw", completion: { response in self.token = response.token } } }
// Production class LogInViewController: UIViewController { var authService: AuthServiceType! var
token: String func logInButtonDidTap() { authService.logIn(id: "id", pw: "pw", completion: { response in self.token = response.token } } } // Test func testLogIn() { ... XCTAssertEqual(viewController.token, "token") // Succeeded }
Various cases of response - Success or Failure (Wrong password)
class StubAuthService: AuthServiceType { func logIn(id: String, pw: String, completion:
(Response) -> Void) { let response = Response(token: "token") // Success case completion(response) } }
class StubAuthService: AuthServiceType { func logIn(id: String, pw: String, completion:
(Response) -> Void) { let response = Response(token: "token") // Success case completion(response) } } func testLogIn_success() { ... XCTAssertEqual(viewController.token, "token") } func testLogIn_wrongPassword() { ...? }
class StubAuthService: AuthServiceType { var stubResponse: Response! func logIn(id: String,
pw: String, completion: (Response) -> Void) { completion(stubResponse) } }
class StubAuthService: AuthServiceType { var stubResponse: Response! func logIn(id: String,
pw: String, completion: (Response) -> Void) { completion(stubResponse) } } func testLogIn_success() { let authService = StubAuthService() authService.stubResponse = Response(token: "token") ... viewController.logInButtonDidTap() XCTAssertEqual(viewController.token, "token") }
class StubAuthService: AuthServiceType { var stubResponse: Response! func logIn(id: String,
pw: String, completion: (Response) -> Void) { completion(stubResponse) } } func testLogIn_success() { ... } func testLogIn_wrongPassword() { let authService = StubAuthService() authService.stubResponse = Response(error: LogInError.wrongPassword) ... viewController.logInButtonDidTap() XCTAssertEqual(viewController.logInError, LogInError.wrongPassword) }
class StubAuthService: AuthServiceType { var stubResponse: Response! func logIn(id: String,
pw: String, completion: (Response) -> Void) { completion(stubResponse) } } func testLogIn_success() { ... } func testLogIn_wrongPassword() { let authService = StubAuthService() authService.stubResponse = Response(error: LogInError.wrongPassword) ... viewController.logInButtonDidTap() XCTAssertEqual(viewController.logInError, LogInError.wrongPassword) }
enum LogInError { case wrongPassword case idNotExist } class LogInViewController:
UIViewController { var authService: AuthServiceType! var token: String var logInError: LogInError func logInButtonDidTap() { authService.logIn(id: "id", pw: "pw", completion: { response in if response.status == .success { self.token = response.token } else if response.status == .failure { self.logInError = response.error } } } }
enum LogInError { case wrongPassword case idNotExist } class LogInViewController:
UIViewController { var authService: AuthServiceType! var token: String var logInError: LogInError func logInButtonDidTap() { authService.logIn(id: "id", pw: "pw", completion: { response in if response.status == .success { self.token = response.token } else if response.status == .failure { self.logInError = response.error } } } } func testLogIn_wrongPassword() { ... // Succeeded XCTAssertEqual(viewController.logInError, LogInError.wrongPassword) }
Stubber
Stubber created by @devxoul Stubber
Stubber is a minimal method stub for Swift.
Stubber is a minimal method stub for Swift. https://github.com/devxoul/Stubber
Summary Tests may fail because of network dependency …so let’s
write test codes with Test Stub May the Test Stub be with you.
Thank you! Wooseong Kim, StyleShare
[email protected]
#swiftkorea