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
Async network logic testing using Protocol (Tes...
Search
Wooseong Kim
December 13, 2017
Programming
2
360
Async network logic testing using Protocol (Test Stub)
presented at Swift Korea meetup (2017/12/13)
Wooseong Kim
December 13, 2017
Tweet
Share
More Decks by Wooseong Kim
See All by Wooseong Kim
Tuist, 도입은 했는데 그래서 Modular Architecture 는 어떻게 만들어 가나요?
innocarpe
0
340
Modular Architecture w/ Tuist
innocarpe
0
540
Test-Driven-Development with ReactorKit in StyleShare
innocarpe
1
880
Developing iOS with Rx, MVVM
innocarpe
1
150
git + Pull Request + Code Review and Project Management with Agile
innocarpe
0
69
Online Board(Trello), Scrum(Pivotal Tracker), Kanban(JIRA Agile)
innocarpe
0
130
Android TV at Google Developers Summit
innocarpe
0
73
경북대도서관 스마트앱 최종발표
innocarpe
0
180
Other Decks in Programming
See All in Programming
Arm移行タイムアタック
qnighy
0
330
Make Impossible States Impossibleを 意識してReactのPropsを設計しよう
ikumatadokoro
0
200
よくできたテンプレート言語として TypeScript + JSX を利用する試み / Using TypeScript + JSX outside of Web Frontend #TSKaigiKansai
izumin5210
6
1.7k
CSC509 Lecture 11
javiergs
PRO
0
180
Quine, Polyglot, 良いコード
qnighy
4
640
Nurturing OpenJDK distribution: Eclipse Temurin Success History and plan
ivargrimstad
0
940
OSSで起業してもうすぐ10年 / Open Source Conference 2024 Shimane
furukawayasuto
0
110
役立つログに取り組もう
irof
28
9.6k
Better Code Design in PHP
afilina
PRO
0
130
subpath importsで始めるモック生活
10tera
0
310
as(型アサーション)を書く前にできること
marokanatani
10
2.7k
Streams APIとTCPフロー制御 / Web Streams API and TCP flow control
tasshi
2
350
Featured
See All Featured
Performance Is Good for Brains [We Love Speed 2024]
tammyeverts
6
410
Understanding Cognitive Biases in Performance Measurement
bluesmoon
26
1.4k
Git: the NoSQL Database
bkeepers
PRO
427
64k
Intergalactic Javascript Robots from Outer Space
tanoku
269
27k
Side Projects
sachag
452
42k
CoffeeScript is Beautiful & I Never Want to Write Plain JavaScript Again
sstephenson
159
15k
The Art of Programming - Codeland 2020
erikaheidi
52
13k
Designing for humans not robots
tammielis
250
25k
Practical Tips for Bootstrapping Information Extraction Pipelines
honnibal
PRO
10
720
ピンチをチャンスに:未来をつくるプロダクトロードマップ #pmconf2020
aki_iinuma
109
49k
4 Signs Your Business is Dying
shpigford
180
21k
A Modern Web Designer's Workflow
chriscoyier
693
190k
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