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 (Test Stub)
Search
Wooseong Kim
December 13, 2017
Programming
2
340
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
150
Modular Architecture w/ Tuist
innocarpe
0
420
Test-Driven-Development with ReactorKit in StyleShare
innocarpe
1
770
Developing iOS with Rx, MVVM
innocarpe
1
120
git + Pull Request + Code Review and Project Management with Agile
innocarpe
0
56
Online Board(Trello), Scrum(Pivotal Tracker), Kanban(JIRA Agile)
innocarpe
0
110
Android TV at Google Developers Summit
innocarpe
0
68
경북대도서관 스마트앱 최종발표
innocarpe
0
140
Other Decks in Programming
See All in Programming
Tailwind CSSを本気でカスタマイズする方法
fsubal
13
5.3k
障害対応を起点としたもっといい開発と運用のサイクル作りのためにできること / Hatena Enginner Seminar #29
polamjag
0
180
Goのエラースタックトレースの歴史と今後
sonatard
9
1.5k
エンターテイメント業界で利用されるAWS
demuyan
0
210
Random\Randomizer クラスで日常のあれこれを解決しよう! / Random\Randomizer class solves familiar trouble
cocoeyes02
0
250
DMMプラットフォームがTiDB Cloudを採用した背景
pospome
8
4.1k
VS Code をプロダクトにどう取り込むか
onomax
1
370
Anthropic Cookbook のおすすめレシピ
schroneko
7
980
1BRC--Nerd Sniping the Java Community
gunnarmorling
0
340
Fast JSX: Don't clone props object #28768
yossydev
1
120
ADRを一年運用してみた/adr_after_a_year
hanhan1978
7
2.4k
はてなにおける CSS Modules、及び CSS Modules に足りないもの / CSS Modules in Hatena, and CSS Modules missing parts
mizdra
7
930
Featured
See All Featured
Building an army of robots
kneath
300
41k
How GitHub (no longer) Works
holman
304
140k
I Don’t Have Time: Getting Over the Fear to Launch Your Podcast
jcasabona
21
1.6k
How to Ace a Technical Interview
jacobian
272
22k
Building Your Own Lightsaber
phodgson
99
5.7k
Designing Dashboards & Data Visualisations in Web Apps
destraynor
226
51k
How STYLIGHT went responsive
nonsquared
92
4.8k
4 Signs Your Business is Dying
shpigford
175
21k
個人開発の失敗を避けるイケてる考え方 / tips for indie hackers
panda_program
60
14k
BBQ
matthewcrist
80
8.8k
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
121
39k
XXLCSS - How to scale CSS and keep your sanity
sugarenia
241
1.2M
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