$30 off During Our Annual Pro Sale. View Details »

Swiftでテスト時のみ『private』にアクセスする方法

 Swiftでテスト時のみ『private』にアクセスする方法

mobile_stmn #1 で登壇した際の資料になります

Ryu-nakayama

July 14, 2023
Tweet

More Decks by Ryu-nakayama

Other Decks in Programming

Transcript

  1. © Chatwork
    Swiftでテスト時のみ
    『private』にアクセスする方法
    2023年7月14日 モバイルアプリケーション開発部 中山 龍
    Chatwork株式会社

    View Slide

  2. 自己紹介
    2
    中山 龍 (なかやま りゅう)
    ● Chatwork株式会社
    ○ モバイルアプリケーション開発部
    ○ iOSエンジニア
    ○ 2023年4月 新卒として入社
    ○ 社内最年少 (2002年6月生まれの21歳)
    ● 愛知県在住
    ○ フルリモート勤務
    ● 初めてのLTで緊張してます!
    @ryu_develop

    View Slide

  3. 3
    © Chatwork
    はじめに

    View Slide

  4. 4
    テストを書いている時に...

    View Slide

  5. 5
    テストを書いている時に
    『private』にアクセスしたいな
    って場面、ありませんか?

    View Slide

  6. 例えば...テスト中に
    Privateな
    ● 値をリセットしたい
    ● 値を追いたい
    ● メソッドをテストしたい
    6
      は避けた方が良い場合も...  
    終盤でお話しします

    View Slide

  7. テストのためにprivateにアクセスしたいとき、どうすればいいんだろう?
    7

    View Slide

  8. 今回扱う状況・問題
    1

    View Slide

  9. 今回想定する状況
    9
    class Sample {
    public static let shared = Sample()
    private var value = 0
    }
    ● privateなプロパティ『value』を持っているclass
    ● シングルトン

    View Slide

  10. このclassをテストする際の問題点
    10
    ● classをテストごとにインスタンス化ができない

    🚨
    シングルトンならではの問題が起きる

    View Slide

  11. このclassをテストする際の問題点
    11
    ● classをテストごとにインスタンス化ができない
    ● privateなプロパティをテストごとにリセットできず、値がテ
    ストを超えて引き継がれてしまう

    🚨
    シングルトンならではの問題が起きる

    View Slide

  12. このclassをテストする際の問題点
    12
    ● classをテストごとにインスタンス化ができない
    ● privateなプロパティをテストごとにリセットできず、値がテ
    ストを超えて引き継がれてしまう
    → テストの結果が実行順に左右されてしまう
    🚨
    シングルトンならではの問題が起きる

    View Slide

  13. 13
    変更した値が次のテストにも引き継
    がれるのを回避したい

    View Slide

  14. 14
    『private』なプロパティにアクセス
    して、値をリセットしたい

    View Slide

  15. 何も考えずに『private』にアクセスするとしたら?
    2

    View Slide

  16. class Sample {
    public static let shared = Sample()
    private var value = 0
    func getter() -> Int {
    return value
    }
    func setter(value: Int) {
    self.value = value
    }
    }
    何も考えずに『private』にアクセスするとしたら
    16
    getterとsetterを持たせる

    privateへのget / set が可能
    になる

    View Slide

  17. 17
    アクセスできるけど...
    これだとprivateにした意味がない!
    テストの時だけgetterとsetterを使える
    ようにできないでしょうか?

    View Slide

  18. 『テスト』を判定してアクセスを可能にする
    3

    View Slide

  19. 『テスト』のビルドを判定する
    19
    iOS開発ではビルドの種類を判定する仕組みがある
    #if DEBUG
    // デバッグ時の処理
    #endif
    以下は、「デバッグビルドの際に処理が走る」という例
    デバッグ
    ビルド
    デバッグ時
    の処理
    YES
    NO

    View Slide

  20. 『テスト』のビルドを判定する
    20
    この分岐は「Build Configuration」によって
    判定されている
    デフォルトで存在するBuild Configurationは
    ● Debug
    ● Release
    の2種類

    View Slide

  21. 『テスト』のビルドを判定する
    21
    この「Build Configuration」に「Test」を追加すれば、
    「テスト」のビルドを判定できるのではないか?

    View Slide

  22. 『Build Configuration』を追加する(手順解説)
    4

    View Slide

  23. Build Configurationの追加方法
    23
    PROJECT 内の Info で Configurations左下の+ をクリック
    し、Duplicate “Debug” Configurationを選択
    生成されたものの名前をTestにする
    1.

    View Slide

  24. Build Configurationの追加方法
    24
    Edit Schemeを開き、TestのBuild ConfigurationでTestを
    選択する
    2.

    View Slide

  25. Build Configurationの追加方法
    25
    PROJECT の Build Settings にある Active Compilation
    Conditions で Testキー の値を TESTにする
    3.

    View Slide

  26. 26
    こうすることでBuild Configurationが追加され、
    #if TESTで「テスト」を判定できるようになる

    View Slide

  27. 実際に分岐させる
    5

    View Slide

  28. class Sample {
    public static let shared = Sample()
    private var value = 0
    #if TEST
    func getter() -> Int {
    return value
    }
    func setter(value: Int) {
    self.value = value
    }
    #endif
    }
    テストで分岐させてみる
    28
    getterとsetterを
    #if TEST 〜 #endifで囲むと、
    テスト以外では存在しないこと
    にできる

    View Slide

  29. つまり
    29
    class Sample {
    public static let shared = Sample()
    private var value = 0
    func getter() -> Int {
    return value
    }
    func setter(value: Int) {
    self.value = value
    }
    }
    テストビルド時
    class Sample {
    public static let shared = Sample()
    private var value = 0
    }
    テスト以外のビルド時
    テストビルド以外の際にはgetter
    とsetterが無い扱いとなる

    View Slide

  30. 30
    通常時はprivateのメリットを生かしつつ、
    テストの時には『private』にアクセス
    できるようになった!!

    View Slide

  31. 補足
    31
    「#if TEST」で囲まれた部分を呼び出すコードを書くと
    Xcodeに警告される。
    それは、おそらく静的解析がテストビルド以外の
    Configurationで走っているためである。(予想)
    警告を無視しても問題なくビルドできる

    View Slide

  32. 他にも...
    32
    この#if TEST 〜 #endifの書き方を応用すること
    で、テスト内で自由に『private』にアクセスした
    り、テスト時のDIに活用したりすることができる

    View Slide

  33. 33
    でも...注意する点があります

    View Slide

  34. テストで『private』にアクセスする際の注意
    6

    View Slide

  35. 注意点
    35
    冒頭でも少しお話ししたようにprivateに対するテストは避
    けた方が良い場合もあります

    View Slide

  36. 注意点
    36
    自動テストを書く理由の一つとして「リファクタリングの支えになる」という点があ
    ります
    ● リファクタリング:「外部から見た振る舞いを変えずに内部の実装をきれいにするこ
    と」
    t-wadaさんのブログ: プライベートメソッドのテストは書かないもの? より引用し、要約
    https://t-wada.hatenablog.jp/entry/should-we-test-private-methods

    View Slide

  37. 注意点
    37
    自動テストを書く理由の一つとして「リファクタリングの支えになる」という点があ
    ります
    ● リファクタリング:「外部から見た振る舞いを変えずに内部の実装をきれいにするこ
    と」
    しかし、privateに対するテストは「内部の実装に対するテスト」になってしまうこ
    とが多い
    → リファクタリングの妨げになりがち
    t-wadaさんのブログ: プライベートメソッドのテストは書かないもの? より引用し、要約
    https://t-wada.hatenablog.jp/entry/should-we-test-private-methods

    View Slide

  38. 注意点
    38
    privateに対してのテストを書いてしまうと
    自動テストで積極的にリファクタリングを行いたい

    自動テストがリファクタリングの妨げになる
    という場合も...
    → テスト時に『private』にアクセスする場合は、この
    ような状況に気をつけましょう

    View Slide

  39. まとめ
    7

    View Slide

  40. まとめ
    40
    ● Build Configurationを追加することで、テストのビルドを判
    定して、適切な処理の分岐が可能になる
    ○ 今回紹介した方法で 「#if TEST」 が使えるようになる

    View Slide

  41. まとめ
    41
    ● #if TESTが使えると...
    ○ 通常ビルドではprivateの恩恵を受けつつ、テスト時には
    privateにアクセスして、値のリセットなどができる
    ○ テスト用にDIをする処理を書くなど、使い方によって様々
    なことが可能になる

    View Slide

  42. まとめ
    42
    ● ただし、privateに対してテストを書く・値を操作する時には、
    それがリファクタリングの妨げにならないように気をつける

    View Slide

  43. 働くをもっと楽しく、創造的に

    View Slide