Swiftのモックメモ(自分用)

# Swiftのモックメモ(自分用)
## この記事を書こうと思ったきっかけ
2023年5月現在、Swiftにおけるモック系の記事がマジでないんですよね
2018〜2019年あたりはよく見かけるけど、一年以内に絞ると一気に無くなる印象
仕事で自動テスト入れよか〜って話して調べてるけど、各パッケージの情報が古かったりして今使えなかったりしました~~まぁApple容赦ないし~~
こんがらがっても来たので、現時点で分かったことを自分なりにまとめることにしました~~ツッコミはいくらでもください~~
自分用メモとしてまとめたものなので、雑文になりますがご容赦くださいm(_ _)m

## メモ1「Swiftでモックする意味とは？」
モックを行う目的としては、[テスト駆動開発](https://ja.wikipedia.org/wiki/%E3%83%86%E3%82%B9%E3%83%88%E9%A7%86%E5%8B%95%E9%96%8B%E7%99%BA)や自動テストで、外部環境に関わらずテストコードを作りたいというケースで活躍します。
モックをすることにより、まだ未作成のクラスをモック化したり、各クラスをテストする際の振る舞いをテスト時に決められたりし、開発の幅が広がります。

テストする意味については以下な感じです
前者の場合は「良いコード/悪いコードで学ぶ設計入門」か「テスト駆動開発」のどちらかの神書を読むと良いと思います。~~ぶっちゃけ先にテスト作ってからコード書こうぜ！ってだけらしいですが~~
後者の場合、運用・保守のテストの工程が減らせたり、開発者が楽になるって感じです~~100のテストケースがるテスト仕様書を何回も作ると流石に目が死んできます~~


## メモ2「通信のモック」
モックには、「通信のモック」と「依存オブジェクトのモック」の2種類があります(他にあるんですかね……？)

ここでは通信のモックについて記載します。
ユニットテストでは、外部環境との兼ね合いや、テストの実行速度などの理由で、外部と繋ぎたくないというケースが多いと思います。
そこで、API通信をモックすれば、外部環境にかかわらず、ユニットテストが行えます。

方法としては
(1).URLSession自体の処理を置き換える
(2).HTTP接続先をモックする
という方法が取られるみたいですね

ちなみに(1)は[class_getInstanceMethod](https://developer.apple.com/documentation/objectivec/1418530-class_getinstancemethod)のあたりを使って置き換えるっぽいですね。
このメソッドはObjective-Cのメソッドなので、Foundation経由で使うものが対象みたいです（URLSessionがそれなのでAPIを置き換えれる）
各パッケージは多分これを使いやすくしているものかなと思っています。

(2)については、一部のパッケージ([Shock](https://github.com/justeat/Shock)とか)で使えるみたいです。
これを使えばURLSessionを~~黒魔術~~メタプログラミングを使わずに、端末内のHTTPサーバーでやり取りが行えるのでより厳密に行えるのかも？
紹介しといてなんですが使ってはないです。

## メモ3「依存オブジェクトのモック」
DI(依存性の注入)のことです。
依存性だとわかりにくいので、依存オブジェクトと勝手に呼んでいます。

要は、オブジェクトの外部から中で使用しているオブジェクトを注入してあげることです。
↓こんな感じ

``` swift
class FugaClass {}
class HogeClass {
    // HogeClassはFugaClassのメンバ変数を持つ ＝ FugaClassに依存している
    var fuga: FugaClass
    // イニシャライザなどで外部から注入する
    init(fuga: FugaClass) {
        self.fuga = fuga
    }
}

let fuga = FugaClass()
let obj = HogeClass(fuga)
```

Swiftのメソッドの書き換えを無理やり行えないみたいです（Objective-C由来なら可能なはず）
なので、プロトコルを経由することで、突破できます。それがメモ4です。

// TODO: ここから書いていく
## メモ4「Swiftでモックが作りにくい」
Swiftのモックの理解は[この記事](https://ikesyo.hatenablog.com/entry/2019/12/17/114034 "2019年のSwiftモック事情")がわかりやすかった。~~いやほんと、いけしょーさん本当にありがとうございます~~
私自身、理解が浅いので説明として合っているかがわかりませんが、Swiftは静的ランタイムを採用しているため、~~黒魔術~~メタプログラミングによる強引な処理の置き換えが使えないみたいです。
反面、Objective-Cは動的ランタイムなので、Objective-CのフレームワークであるFoundation由来なら置き換えができるらしいです（URLSessionが置き換えられるのはこれが理由）
なので、Swiftでモックを使うには、以下のような処理が必要になります

(1).CockProtocolをWaiterに注入できるようにする
(2).本番時はデフォルト引数などで、Cockオブジェクトを渡す
(3).テスト時はMockCockオブジェクトを渡す

クラス図としてはこう（~~※見よう見まねのクラス図なので間違いがあったらすみません~~）
```plantuml
interface CockProtocol
class Waiter
class Cock
class MockCock

Waiter --* CockProtocol
CockProtocol : order() : bool
Cock ..> CockProtocol
MockCock ..> CockProtocol
```
そのため、Swiftではモック作成をするためには
* テスト対象のクラス
* モック対象のクラス
* モッククラス
* 置き換え用プロトコル
が必要になります

もちろん、モッククラスの挙動も定義する必要が出てきますし……~~面倒くさい~~
※一応、オーバーライドを利用すればプロトコルは不必要になるっぽいが、ネットで見かける情報は以上のやり方しか見当たらなかった

## メモ5 「Swiftにおけるデファクトスタンダートがない」
Androidのデファクトスタンダードとして、「Mockito」がよく挙げられています
ではSwiftだと？という回答に答えている記事が見られませんね
本当、皆さん何使ってるんですかね？プロジェクトによると思いますが、結構気になってます

有名どころとしては以下なのかなぁと思います

### 通信モックのパッケージ
* #### [OHHTTPStub](https://github.com/AliSoftware/OHHTTPStubs)
まずはこれが筆頭ですかね？
約5kスターというところから、人気の高いパッケージであることがわかりますね
しかし、コードもドキュメントもObjective-Cで書かれているため、Swiftから触ったエンジニアからは拒否感が出そうですね（かくゆう私もですが）

* #### [Mockingjay](https://github.com/kylef/Mockingjay)
　[iOSアプリ開発自動テストの教科書〜XCTestによる単体テスト・UIテストから、CI/CD、デバッグ技術まで](https://www.amazon.co.jp/iOS%E3%82%A2%E3%83%97%E3%83%AA%E9%96%8B%E7%99%BA%E8%87%AA%E5%8B%95%E3%83%86%E3%82%B9%E3%83%88%E3%81%AE%E6%95%99%E7%A7%91%E6%9B%B8%E3%80%9CXCTest%E3%81%AB%E3%82%88%E3%82%8B%E5%8D%98%E4%BD%93%E3%83%86%E3%82%B9%E3%83%88%E3%83%BBUI%E3%83%86%E3%82%B9%E3%83%88%E3%81%8B%E3%82%89%E3%80%81CI-CD%E3%80%81%E3%83%87%E3%83%90%E3%83%83%E3%82%B0%E6%8A%80%E8%A1%93%E3%81%BE%E3%81%A7-%E5%B9%B3%E7%94%B0-%E6%95%8F%E4%B9%8B/dp/4297106299)（通称、教科書本）の本でも紹介されているほどに有名なパッケージです
　なお、問題点としては[「Xcode13以降でエラーが出るようになった」](https://ios-docs.dev/mockingjay/)ことでしょうか。2023年現在のApp Store環境では[「Xcode14.1以降出ないとリリースできない」](https://developer.apple.com/jp/news/?id=jd9wcyov)ので、結構辛いですね。2019年以降のリリースもありませんし……

* #### [Mocker](https://github.com/WeTransfer/Mocker)
うちのプロジェクトで採用が決定したパッケージです
GitHubの外国の方のやり取りの中でMockerが勧められており、使ってみましたが、クセがなく使いやすい印象でした。

### 依存性のモック
[このサイト](https://logmi.jp/tech/articles/322551)に書かれていることが参考になると思います
Swiftでは[上記](##-メモ4「Swiftでモックが作りにくい」)の問題を解決するため、Swiftのモッククラスを自動作成しようぜ！というパッケージが続々と登場しました
有名どころとしては以下になりますかね
* #### [Soucree](https://github.com/krzysztofzablocki/Sourcery)
Swiftのコードジェネレーターの代表格といえばこいつ
以下にあげるSwiftyMockyは裏でこのパッケージを使っています
汎用的なコードジェネレータですが、デフォルトで用意してくれているテンプレに、モッククラスを生成するものがあります
他にも色々とクセがあり、学習コストが高くなってしまうのがネックです

* #### [SwiftyMocky](https://github.com/MakeAWishFoundation/SwiftyMocky)
Sourceryよりも高機能なモックを利用できるパッケージです
CLIが必須ですが、異なるパッケージ管理ツールでダウンロードする必要があるため、管理が面倒くさいというのがネックになります

* #### [Cuckoo](https://github.com/Brightify/Cuckoo)
教科書本でも使い方が解説されていたコードジェネレーターです
私の参画しているプロジェクトでも採用されました
Sourceryではない、独自のジェネレーターを使用しています
モックに必要な機能は一通り揃っているので、高機能なパッケージといえます
難点は、CLIにどのファイルのモックを作るのかを指定する必要があるのですが、複数人で開発を進めると、そこでコンフリクトする点ですね……どうにかならんか

* #### [Mockolo](https://github.com/uber/mockolo)
uber社が提供しているコードジェネレーターです
特徴はなんといっても早いことだそうです
Soucery、Cuckooで2時間以上かかっていたソースコードの解析・生成を直列なのに17秒で終わらせるという圧巻の速さ！
最適化して並列で処理をすると5.3秒に短縮するそうです速い！！！

しかし、２０２３年現在、依存しているパッケージの設定の問題なのか、SPM経由でインストールすると、Build Targetが違うと怒られ、Mintではインストールが失敗します（おま環の可能性が高いですが）
なので、私のプロジェクトでは考慮外へ。結構期待してたんだけどなぁ……

* #### [Mokingbard](https://github.com/birdrides/mockingbird)
このモックは私がGitHubから探してきたパッケージです
なんといってもSwiftyMocky, Cuckoo, Mockoloを凌ぐ高機能を備えているパッケージであること！（公式が提供している[機能比較表](https://mockingbirdswift.com/feature-comparison)）
……という触れ込みなんですが、ジェネリクスができなかったんですよねぇー……できるって書いてあるんだけどなー……
CLIを設定する際にファイルを指定する必要がないので、Gitで管理する際にかなり楽になるなーと思って期待してたんですけど、ジェネリクスは必須なので、んー、残念

## 終わりに
高品質なソースコードを生み出すことが、そのプロジェクトの成功の鍵であると私は思っています
そのためには良いテストを行い、品質を担保する必要が出てきます
良いテストのためには、適切な粒度のテストを組む必要があり、そうなると、モックの話題は必ず挙がるものだと認識しています
iOS開発では、モックのデファクトスタンダードが存在しないため、散見する情報を一つにまとめ、自分のプロジェクトに如何に適合させるかが重要ではないでしょうか

折角なので、2023年4月に非常に興味深い論文とネットの記事が投稿されましたので、一緒にご紹介します
[「コード品質はやはりビジネスに影響を与える」](https://mtx2s.hatenablog.com/entry/2023/04/26/230917)という記事では、高品質なコードと低品質なコードを比較すると、平均で2倍、最大で9倍の開発期間になるとあります
このことから、コストを抑えるという点を踏まえても、高品質なコードは低品質なコードに勝ると言えます（~~だからコストは低品質なコードを生産する理由にはならなくなります~~）

プロジェクトの成功のためにも、モックの導入を検討してみては如何でしょうか