Testable Swift、あるいは僕らがテストを書かない理由

  • 18
    いいね
  • 0
    コメント

この記事は、「Speee Advent Calendar 2016」の6日目です。

iosアプリエンジニアはテスト1を書かない?

TDDやテストコードを書く重要性が叫ばれて久しいですが
日々業務でiosアプリ2のコードを書いていると中々テストを書く機会がありません。
また、社外のエンジニアの方とお話させて頂いても、あまり積極的にテストを書くプロジェクトは少ないように感じます。

この記事ではiosアプリでテストを書かない(書きづらい)理由と、それを突破してテストを書くにはどうすればいいかを考えてみます。

UIのテスト

XcodeにはUIテストツールが含まれています。

コードベースで動作を指定して画面操作をシミュレートすることが可能ですが
あまり有効に活用できている現場は無いように感じます。

おそらく問題点は2つあって
一つ目はシミュレータの動作の遅延などによって再現性の低いバグを引き起こすことがある(しかも実感としてけっこうある)

そのため、UIテストをCIでrequiredなテストにすると開発に不要なボトルネックになりかねないですし、
逆に「CIで落ちても良いや」と言って落ちたままにしているとオオカミ少年的なテストになってしまいます

もう一つはメンテナンスコストの問題で
UITestでテストしたい状態まで画面を進めていくのは結構面倒くさい(ログインとか)上に
画面の改修によって簡単に動かなくなるので、手間の割には効果を受けにくいテストでもあります。

リフレクションが無い = モッククラスを作れない

Swiftにはリフレクションが無く、それ自体はわりと好評だと思う・・のですが、
ことテストに関わる部分でモッククラス(テストダブル)を非常に作りづらいです。
特に他の言語でモッククラスを作りながらテストするのに慣れていると大変な面倒くささを感じるポイントです。

Swiftでモックテスト

Swiftでモックによるテストを実現しようとするとDIの設計を考慮する必要があります
ただDI自体もリフレクション無しで実現しようとすると、テスト対象の実装コードをDI出来る形にちゃんと設計する必要があり、場合によっては大幅な改修が発生します。
テストを書くために本体のコードを書き換えるというのはリファクタリングの一つではあるのですが、
ある程度出来上がった実装からやっていくのは中々しんどいかなとは思います。

特に通信まわりなどの低レイヤーにあるコードは、他の多くのコードから依存されるため
テストを書く際のボトルネックになりがちです(なった)。
逆にこういったコードをDI出来るような改修ができれば、一気にテストを書きやすくなるでしょう。

マルチスレッドのテスト

iosアプリはもともとマルチスレッドを前提に作られていますが、
このマルチスレッドであるということと単体テストがとにかく相性悪いです

いわゆるレース状態による銀行口座のバグなどがよくある例ですが、
こういったマルチスレッド特有のバグを検証しようとすると

  • インスタンスに変更を加える全ての処理毎に前後関係をテストする
    • 無限にテストが増えるので無理
  • 大量にスレッドを生成してたまたまバグが発生するのを待つ

といったアプローチで、バグを見つける可能性を高めることは出来ますが
バグを見つけきるのは非常に難しいです。

テストカバレッジの高さ ≠ 品質

「テストを書く」という行為はソフトウェアの品質をなんらかの形で向上させようとする行為といえます。
品質という目に見えないものを見える化するための指標(メトリクス)としてはテストカバレッジやバグ率、クラスの結合度といったものがあります

アプリのコードは上記のマルチスレッドの問題とあいまって
カバレッジが100%でも起きるバグがある(しかも結構ある)
-> カバレッジの高さ ≠ 品質
-> つまりいろんなテストを書いてカバレッジを上げる作業と品質の改善に相関が薄い

これは実感としても、テストカバレッジを頑張って向上させるよりも
さっさと実機テストのフェイズに進んで
適当に画面ポチポチしていたほうがバグの発見につながるように思います。

この部分は、基本的にシングルスレッドで動作するrailsの世界でテストカバレッジが品質指標として重視されていることと対比すると面白いです。

iosアプリのテストを書くには

プロジェクトの客観的指標としてテストカバレッジなどを目標設定しても品質的な効果は薄いと言えます。
そうするとテストを書くモチベーションは、逆に主観的な安心感を改善させるために、「テストを書きたいときに書ける」環境つくりが重要に思えます。

以下にiosのテストを改善させるためのポイントをまとめてみました。
開発の参考にして頂ければ幸いです。

プロジェクトとして

  • まずはCIを用意してテストが実行される環境を作る
    • 主体的にテストを書きたいと思った人が書ける環境づくり
  • 作ったテストは必ずメンテナンスする
  • CIが落ちても実装が進むような不健全な状態を避ける
    • 逆に、費用対効果が合わずにメンテナンスできないようなテストコードは、その時点では必要以上のテストであると判断出来るはず
  • プロジェクトとしてのテスト基準を作るときは慎重に決める

マルチスレッドのテスト

  • 参照透過性を意識してコードを書く
  • 可能な限り変数をletで宣言しimmutableな値を参照させるようにする
  • 少なくとも複数のスレッドから更新される値を極力無くす

テストモックについて

  • タイミングを見てDI可能な設計に切り替えていく
    • 工数はかかるが効果は大きい

脚注


  1. 以下、「テスト」はXCTestやQuickを前提とした単体テストを指します 

  2. 今回の話ではライブラリ開発ではなく、ストアに公開するiosアプリの開発者を想定しています 

この投稿は Speee Advent Calendar 20166日目の記事です。