Save 37% off PRO during our Black Friday Sale! »

【TCA】書きやすくて分かりやすい!Reducerのテストの基本 / [TCA] the basics of testing in Reducer

13d936e697fe0f4fa96f926d0a712f6c?s=47 Sansan
PRO
October 06, 2021

【TCA】書きやすくて分かりやすい!Reducerのテストの基本 / [TCA] the basics of testing in Reducer

■イベント

After iOSDC Japan 2021
https://zozotech-inc.connpass.com/event/222423/

■登壇概要

タイトル:【TCA】書きやすくて分かりやすい!Reducerのテストの基本

登壇者:技術本部 Seminar One Engineeringグループ 池端 貴恵

▼Sansan Engineering

https://jp.corp-sansan.com/engineering/

13d936e697fe0f4fa96f926d0a712f6c?s=128

Sansan
PRO

October 06, 2021
Tweet

Transcript

  1. 【TCA】書きやすくて分かりやすい! Reducerのテストの基本 Sansan株式会社 技術本部 Seminar One Engineeringグループ 池端 貴恵 A

    f t e r i O S D C J a p a n 2 0 2 1
  2. Sansan Seminar Managerを作っています。 ちょっと前は、EightのiOSアプリをつくってました。 池端 貴恵 Sansan株式会社 技術本部 Seminar One

    Engineeringグループ 写真
  3. Agenda - The Composable Architectureとは - テストしたいこと - 同期的な処理のテスト -

    ⾮同期的な処理のテスト - テスト失敗 - まとめ
  4. The Composable Architectureとは

  5. The Composable Architecture → The Composable Architectureは、コンポジション、テスト、エルゴノミクス(※ハードウェアやソフ トウェアなどを、快適で使いやすい道具にするための設計・デザイン)を考慮した、⼀貫性のある理解 しやすい⽅法でアプリケーションを構築するためのライブラリです https://github.com/pointfreeco/swift-composable-architecture

  6. The Composable Architecture https://medium.com/swlh/the-composable-architecture-visualize-data-flows-with-a-diagram-817306831508

  7. The Composable Architecture https://medium.com/swlh/the-composable-architecture-visualize-data-flows-with-a-diagram-817306831508 enum Action: Equatable { case buttonTapped

    }
  8. The Composable Architecture https://medium.com/swlh/the-composable-architecture-visualize-data-flows-with-a-diagram-817306831508 enum Action: Equatable { case buttonTapped

    } let reducer = Reducer<State, Action, Environment> { state, action, environment in switch action { case .buttonTapped: state.count += 1 return .none } }
  9. The Composable Architecture https://medium.com/swlh/the-composable-architecture-visualize-data-flows-with-a-diagram-817306831508 let reducer = Reducer<State, Action, Environment>

    { state, action, environment in switch action { case .buttonTapped: count += 1 return .none } } struct Action: Equatable { var count = 0 }
  10. The Composable Architecture https://medium.com/swlh/the-composable-architecture-visualize-data-flows-with-a-diagram-817306831508 struct Action: Equatable { var count

    = 0
  11. The Composable Architecture https://medium.com/swlh/the-composable-architecture-visualize-data-flows-with-a-diagram-817306831508 let reducer = Reducer<State, Action, Environment>

    { state, action, environment in switch action { case .get: state.count += 1 // var get: () -> Effect<Response, hogeClient.Failure> return environment.hogeClient.get() .receive(on: environment.mainQueue) .catchToEffect() .map(Action.nextAction)
  12. The Composable Architecture PointFree公式解説 iOSアプリ開発のための “The Composable Architecture” がすごく良いので紹介したい

  13. テストしたいこと

  14. TCAで⽤意されているテストについて How to not only test a feature built in

    the architecture, but also write integration tests for features that have been composed of many parts, and write end-to-end tests to understand how side effects influence your application. This allows you to make strong guarantees that your business logic is running in the way you expect. https://github.com/pointfreeco/swift-composable-architecture 基本的なテストの⽅針: ReducerのStateの変更ロジックが正しく実⾏されているかテストする
  15. テストする対象 サインイン画⾯ 要件 - メアドやパスワードが⼊⼒できること - ⼊⼒された内容でログインできること

  16. テスト対象のコード struct LoginState: Equatable { var email = "" var

    password = "" var loginSucceeded = false } enum LoginAction: Equatable { case emailChanged(String) case passwordChanged(String) case loginButtonTapped case login(Result<Bool, LoginClient.Failure>) } struct LoginEnvironment { let loginClient: LoginClient let mainQueue: AnySchedulerOf<DispatchQueue> }
  17. テスト対象のコード struct LoginView: View { let store: Store<LoginState, LoginAction> var

    body: some View { WithViewStore(self.store) { viewStore in VStack(alignment: .center, spacing: 5) { TextField("MailAddress", text: viewStore.binding( get: { $0.email }, send: LoginAction.emailChanged ) ).textFieldStyle(RoundedBorderTextFieldStyle()) .padding() } .... 初期化 LoginView(store: Store(initialState: LoginState(), reducer: loginReducer, environment: LoginEnvironment(省略))
  18. テスト対象のコード let loginReducer = Reducer<LoginState, LoginAction, LoginEnvironment> { state, action,

    environment in switch action { case let .emailChanged(email): state.email = email return .non case let .passwordChanged(password): state.password = password return .none case .loginButtonTapped: // var login: (Email, Password) -> Effect<Bool, LoginClient.Failure> return environment.loginClient.login(state.email, state.password) .receive(on: environment.mainQueue) .catchToEffect() .map(LoginAction.login) case .login(.success): state.loginSucceeded = true return .none case .login(.failure): state.loginSucceeded = false return .none } }
  19. テストすること 1. ⽂字⼊⼒されると、LoginStateのemailとpasswordが正しく更新されるか 2. LoginAPIの結果で、LoginStateのloginSucceededが正しく更新されるか

  20. LoginStateのemailとpasswordに関するテスト (同期的な処理のテスト)

  21. テストコード class loginTests: XCTestCase { let scheduler = DispatchQueue.test func

    test_⼊⼒値チェック() { let store = TestStore( initialState: LoginState(), reducer: loginReducer, environment: LoginEnvironment(loginClient: LoginClient(login:{ _, _ in fatalError()}), mainQueue: scheduler.eraseToAnyScheduler()) ) store.send(.emailChanged("deadbeef")) { // XCTestの XCTAssertEqual($0.email, “deadbeef”) みたいなことをしているのがココ↓ $0.email = “deadbeef” } store.send(.passwordChanged("deliciousbeef")) { $0.password = "deliciousbeef" } } }
  22. LoginStateのloginSucceededに関するテスト (⾮同期的な処理のテスト)

  23. class loginTests: XCTestCase { let scheduler = DispatchQueue.tes func test_ログイン成功()

    { let store = TestStore( initialState: LoginState(), reducer: loginReducer, environment: LoginEnvironment(loginClient: LoginClient(login: { _, _ in Effect(value: true)}), mainQueue: scheduler.eraseToAnyScheduler()) ) store.send(.loginButtonTapped) scheduler.advance() store.receive(.login(.success(true))) { $0.loginSucceeded = true } } テストコード Combineを使ったコードのテストを Schedulerで操る⽅法とその仕組み
  24. テスト失敗😭

  25. 失敗パターン1 テストコード store.send(.emailChanged("deadbeef")) { $0.email = "deadbeef" }

  26. 失敗パターン1 実装 let loginReducer = Reducer<LoginState, LoginAction, LoginEnvironment> { state,

    action, environment in switch action { case let .emailChanged(email): state.password = email <-- 😭 return .none
  27. 失敗パターン2 テストコード store.send(.loginButtonTapped) scheduler.advance() store.receive(.login(.success(true))) { $0.loginSucceeded = true }

  28. 失敗パターン2 実装 let loginReducer = Reducer<LoginState, LoginAction, LoginEnvironment> { state,

    action, environment in switch action { case .loginButtonTapped: return environment.loginClient.login(state.email, state.password) .receive(on: environment.mainQueue) .catchToEffect() .map(LoginAction.hoge) <ー 😭
  29. まとめ TCAにはロジック部分のテストを軽量にかける仕組みがある。 テストを書いて、安定した良いアプリを世に出そう。

  30. 29

  31. 全⽂書き起こしメディア Sansan – 働き⽅を変えるDX – ピアボーナスサービス 契約書データ化サービス スマート台帳 スマート判⼦ クラウド請求書受領サービス

    スマート受付 無⼈名刺受付システム クラウド名刺管理サービス イベント・セミナー 請求書 名刺 契約書 組織コミュニケーション 反社チェックオプション powered by Refinitiv/KYCC 契約管理オプション for クラウドサイン 商談管理オプション for Salesforce アンケートオプション powered by CREATIVE SURVEY powered by MotionBoard 名刺分析オプション 業務連携 名刺作成・発注 データ活⽤ スマートターゲット スマート名寄せ 新世代エントリーフォーム スマートエントリー 新世代パンフレット スマートパンフレット スマート署名取り込み AI名刺管理 オンライン名刺 スマート名刺メーカー スマートフォーム データ統合・活⽤サービス 法⼈向けセミナー管理システム 名刺作成サービス
  32. We are hiring! 新規事業開発 PdM(プロダクトマネジャー) 新規事業開発エンジニア ウェブエンジニア サービス開発エンジニア 社内システム開発 コーポレートエンジニア

    セキュリティーエンジニア (SOC、社内教育・監査) 研究開発 ⾃然⾔語処理研究員 社会科学分野研究員 機械学習研究員 OCR開発技術者 インフラエンジニア R&D DevOpsエンジニア 研究開発エンジニア データエンジニア サービス基盤エンジニア データサイエンティスト サービス開発 SETエンジニア サービス開発エンジニア ウェブアプリケーションエンジニア インフラエンジニア iOSエンジニア Android エンジニア 募集中のポジション詳細はこちら https://jp.corp-sansan.com/recruit/midcareer
  33. テックカンファレンス「Sansan Builders Stage」開催 Sansanのエンジニア情報サイト「Sansan Engineering」 プロダクト、テクノロジー、カルチャーや採⽤情報など、Sansan株式会社のエンジニアリングに関するあらゆる情報を掲載しています。 Sansan Engineering ENTRY HERE

  34. ※ 登録の際、紹介コードの欄に「1 0 0 6 i O S D C

    」と⼊⼒ください。
  35. None