タマネギプログラマーの雑記

たまねぎ剣士的なニュアンス

iOS開発の自動テストについて最近思っていること

この記事はSwift愛好会 Advent Calendar 2017の19日目の記事です。

はじめに

僕は基本的に自動テストは書くべきであると考えている。
最近仕事で触れているコードが中々のレガシーコードであるから、なおさらだ。

レガシーコードに変更を加えるときには、意図せぬ副作用が生じ、プロダクトを壊してしまう可能性が常につきまとう。
そんな時にテストコードがあれば、自分が既存のロジックを意図せずに破壊してしまっていないか知ることができ、作業に対し安心を感じることができる。

残念ながら、僕が最近対峙しているレガシーコードには、充分なテストコードが用意されていなかった。
そのため、否応なしにiOSプロジェクトでのテストについて考えることが多くなってきた。
一方、僕は幸運にして、周りにテストについての知見者が多い。
そういう人たちに相談したりしながら、最近考えていることをとりとめなく書いていこうと思う。

iOSという環境とテストについて

僕は、iOSプロジェクトという環境は、比較的テストを書くモチベーションを下げやすい環境だと考えている。
その理由は大きく2つだ。

  • テストの実行に時間がかかる
  • UIテストなど、テストが書きづらい箇所が存在する

それぞれの理由についてもう少し掘り下げていこう

テストの実行に時間がかかる

XCodeでのテストの実行は、プロジェクトのビルドがはいるため、基本的に遅い。
その間の時間を潰すために、僕はデスクにハンドグリップとハンドスピナーを置いているくらいだ。

『レガシーコード改善ガイド』に、優れた単体テストの条件として次の2つが上げられている。

  • 実行が速い
  • 問題箇所の特定がしやすい

この条件を見ると、こう思うはずだ。 「iOSでの単体テストは、優れたテストになり得ないじゃないか」と。

『レガシーコード改善ガイド』には、またこうも書いてある。

実行に0.1秒もかかる単体テストは、遅い単体テストである。

つまり、XCode上で動く単体テストは、どうあがいても「遅い単体テスト」になってしまい、「優れたテスト」になりえないのである。
この時点で、僕はiOSプロジェクトにおける単体テストは、別環境における単体テストと比べ、相対的にコストパフォーマンスが低くなる宿命にあると考えてしまう。

UIテストなど、テストが書きづらい箇所が存在する

前提として、UIテストを書くことはコストが高い。
高い上に壊れやすい。
また、昨今のアプリケーションはデザインの変更が頻繁に行われるため、メンテナンスコストも非情に高くなる。
結果として、UIテストはコストパフォーマンスが悪い場合が多く、UIテストは書かない、という風になりがちだ

それならばそうで、別の箇所のテストを充分に書ければ良いのだが、iOSのプロジェクトでは概してViewとロジックが密に結合してしまっている。
これは、iOSで多く使われているアーキテクチャMVCであることに起因する(もしくはテストへの知識不足)。
MVCでは、ViewControllerがファットになりやすい傾向にあり、そこにロジックが多く混入してしまいやすいのだ。 その上、ViewControllerはViewからのデリゲートなどが多く、テストが非常に難しい。
結果として、テストで保護できないロジックが多く出てしまう。

ではiOSでテストを書くのは悪手なのか

上までで、iOS環境でのテストを少しdisったわけなのだが、では、iOSでテストを書くのはあまり価値がないことかというとそうではない。
ただ、iOSでのテストは、他環境とは違ったテストとの向き合いかたをしなければならないという風に僕は考えている。

まず、テストの実行が遅い点について考える。
テストの実行は、Carthageの使用など、ビルド時間を短くする工夫で多少ましになるが、それでも0.1秒以下は望めないだろう。
この時点で、iOSのテストは、実行コストが高いテストであることは間違いない しかし、今まで何気なく使っていた、「コスパ」という言葉に目を向ける必要がある。

自動テストにおけるコストは何か。
単純に考えるならば、テストの作成、メンテナンスにかかる労力、実行にかかる時間だろう。
ではパフォーマンスとは何か?
これは、人によっても違うだろうが、少なくとも僕の場合は、「作業時の心理的安全性」である。

そもそも、『レガシーコード改善ガイド』で、速いテストが良いとされているのは、コードの編集都度に、気軽にテストの実行ができるためである。
編集の都度テストを実行することができれば、自分の変更が問題ないかどうか確認しながら作業をすすめることができる。
これが、テストによる心理的安全性の確保である。

では、遅いテストでは何が問題になるのか。
高頻度に実行した際の作業オーバーヘッドが大きくなってしまうのである。

f:id:gaoxin-xixxix:20171218001941p:plain

ならば、長くかかる分、テストの実行頻度を減らして作業すれば、作業能率は下がらないはずである。 もっとも、その分自分の編集による破壊に気づくタイミングは平均して遅くなるが、全く破壊に気づかないことに比べれば遥かに旨味があるだろう。

ただ、その頻度をどれくらいに設定するかと言うのは難しい話で、今のところは、「かかる時間へのコスト間と手に入る安心感が釣り合う程度の頻度」という理解にとどまっている。

テストが書きづらい箇所についてはどうだろうか?
これについてはおそらく多くの人が答えを知っているはずで、MVVMやMVPなどのアーキテクチャを少しづつでも導入してくのが一つの回答になる。
テストしづらいViewControllerから、ViewModelやPresenterといった、ロジックを剥がし取る層を用意してやるのである。
MVCからアーキテクチャを変更できない特別な理由があるのなら、古式ゆかしい~Serviceや~Modelといった名前のモデルたちに可能な限りロジックを移していく。
テスト内にてインスタンスを生成できるオブジェクトは、一般的にテストがしやすい。
そのため、上記の方法で比較的簡単にテスト可能なロジックを増やすことができるはずだ。

また、UIテストについても、いわゆるスモークテストの範囲程度を用意してやる分には充分コストに見合うリターンがあると考えられる。
一般に主導線の大規模な変更は少ないし、書く範囲を限定すれば、その分コストも下がるからだ。

まとめ

結局のところ、テストが僕達にもたらしてくれるものは安心感である。
その安心感は何にも替えがたく、また、手戻り率などの面で定量的なリターンももたらしてくれるだろう。
しかしながらやはり、iOSでのテストとの付き合いというのは、まだまだ考えなければならない部分も多く、自分でもまだ納得のいく答えまでたどり着けていないと考えている。
なので、なにかしらご意見があればご教授いただければなあと思ったりしております。