この記事はDeNA Advent Calendar 2018 その2の最終日の記事です
SWETの平田(@tarappo)です。 アドベントカレンダーも本日で最終日です。 そして、2018年は残りわずかとなりました。
そこで今回は今年おこなわれた「WWDC 2018」の「What's New in Testing」をふりかえってみたいと思います。
「WWDC 2018」の動画やスライドは公開されており、以下から見ることができます。 今だと日本語訳もありますし、見て参考になるものが多いかと思います。
この中から「What's New in Testing」は以下になります。
さて、この「What's New in Testing」で紹介されたのは以下の3種類になります。
- Code coverage
- Test selection and ordering
- Parallel testing
Xcode 10から追加された機能もあれば、ベースの機能があってそれが改良されたものもあります。
今回は、これらの機能の中から主に「Test selection and ordering」と「Parallel testing」について紹介したいと思います。
まだそこまで使ってみた情報が世に転がっていないので、是非とも使ってみて様々な知見を世に公開してもらえると嬉しいです。
Code coverage
Xcode 9.3で改良がおこなわれて動作や精度が向上しています。 実際に発表された例では以下のように改善されています。
- Xcodeでロードして表示されるまでの時間
- Xcode 9:6.5 sec
- Xcode 9.3:0.5 sec
- カバレッジファイル
- Xcode 9:214 MB
- Xcode 9.3:18.5 MB
他にもありますが、今回のメインは残り2つですのでこれぐらいにしておきます。 Code coverageについての話は別途どこかしらで紹介できればと思います。
Test selection and ordering
ここで紹介する「Test selection and ordering」と次に紹介する「Parallel testing」で紹介される機能については、Xcode 10以降のスキームエディタから設定できるようになっています。
その設定場所はXcodeの「Edit Scheme」を選択した後、「Test」を選ぶと表示される以下の画面にあります。
この画面の「Options」を選択すると表示される以下の3項目が今回紹介する機能になります。
この「Test selection and ordering」では以下の2つの項目について紹介します。
- Automatically include new tests
- Randomize execution order
そして次の「Parallel testing」では以下の1つの項目について紹介をします。
- Execute parallel on Simulator
Automatically include new tests
この機能は「新しく作成したテストコードを自動的に実行対象に含めるかどうか」の設定になります。 このオプションを無効にした場合、新規で実装したテストコードは常に実行対象外(disable)になります。 デフォルトはチェックがついた有効の状態になっています。
今までは、テストコードの中からあるテストクラスやテストケースを実行対象外にするということができました。 しかし、これだと新規に追加したテストコードは実行対象になってしまいます。
今回のオプションを有効活用をすれば、新規に実装したテストコードは常に実行対象外にするといったことができます。
今までは「実行対象のテストコードを実行対象外にする」という行為のみでしたが、「実行対象外のテストコードを実行対象にする」という逆のやり方もできるようになったということです。
実際の利用ケースを考えてみましょう。 以下の2つのスキームがあるとします。
- スキームA:定期的に動かすことを想定
- 本オプションを無効にする
- スキームB:nightly(夜間)で動かすことを想定
- 本オプションを有効にする
実装したテストコードは常にスキームBでは実行対象となりますが、スキームAでは実行対象にはなりません。
nightlyで問題なく通りつづけ安定性が確認されたらスキームAのほうで、そのテストコードを実行対象とします。
このようなケースでの使い方もできるようになっています。
このケースはあくまでも例であり、利用方法はいろいろと考えられます。
テストコードを実行対象外にする方法
さて、今回の話からは少し脱線した蛇足的な話になります。 これは開発時において、テストコードを一時的に無視したいときの話です。
Xcodeでテストコードを実装している場合に、あるテストコードを実行対象外にするにはどうしたら良いのでしょうか?
まず、他のテスティングフレームワークはどうなっているのでしょうか? 例えば、JUnit4では@Ignoreアノテーション(JUnit5だと@Disabled)があります。 このアノテーションにはコメントを付与することもできます。
@Ignore("not ready yet")
RSpecだとskipというものがあり、xit
というようにxというprefixをつけることでテストをスキップさせることができます。
またpendingというのもあります。
Xcodeの場合だと以下のようにすることで実行対象外にできます。
- testというprefixを消してしまう
- XCTestではtestというprefixがついているものをテスト対象として判断されます
- Xcodeから対象のクラスまたはケースをdisableにする
Randomize execution order
この機能は「対象としたスキームに含まれるテストコードに対して、テストの実行をランダムでおこなうかどうか」の設定になります。
デフォルトではチェックが付いていない無効状態になっています。 無効の状態だとテストの実行順はアルファベット順になっています。
自動テストをランダムに実行できるメリットはなんでしょうか? 自動テストはそれぞれのテストケースが独立していることが望ましいです。
あるテストケースが他のテストケースに副作用を与える形になっていると、次のような問題が起こる可能性があります。
- テストケースが増えて、実行順序が変わったことによりテストが落ちるようになる
- 特定のテストだけを動かそうとして動かない
つまり、「全てのテストコードを動かせば成功するが、それ以外の方法で動かすと失敗する」という形になったりします。 しかし、副作用を与えているかどうかということに気づかないこともあります。
そのため、ランダムで実行させるとテストケースが独立していないことに気づく一助となったりします。
さて、このような重要な機能ですが、他のテスティングフレームワークではランダム実行についてはどうなっているのでしょうか?
JUnit4はAndroidでも利用されていますが、このバージョンではランダム実行はできません。
しかし、JUnit5からランダム実行ができるようになっています。 また、ランダム実行をしたのと同じ順番で再度実行をするためのseedというものが発行されるようになっています。
それ以外にもアルファベット順とOrderアノテーション順というのがあります。 詳しくは以下のサイトを確認してみてください。
RSpecでは2.8(2012/01)の時点で、すでにランダムで実行できるようになっています。 そして、JUnit5と同様にseedを発行できます。
再度同じ順番で実行ができるというのは、テストの実行をランダムでおこなう場合は必須の機能とも言えます。
この必須ともいえる機能はどのようなときに必要なのでしょう? テストコードをランダムで実行して失敗し、修正をしたあとに直っているかどうかの確認をするためには同じ順番で実行できることが重要です。
しかし、Xcode 10ではランダムでの実行がサポートされましたが、同じ順番で再度実行できないといった状況です。 つまり再現確認をおこなうことが出来ません。
どのような順番で実行されたかは分かるので、手動で頑張るという荒業はあるにはありますが、なかなか厳しいです。 せっかくの機能ではありますが、これについては致命的です。
今後改善されることを期待したいと思います。
なお、「Open Radar」にはそのことについて書かれたチケットがすでにあります。
Parallel testing
この話は「WWDC 2018」では多少多めに時間をとって説明をしていました。
Xcode 9の時点でxcodebuildコマンドからシミュレーターの並列起動ができるようになり、fastlaneのsnapshotでは対応されました。 しかし、以下の機能不足がありました。
- xcodebuildコマンドのみの対応
- destinationはそれぞれ別である必要があった
- 同じタイプのiPhone Simulatorを選択できませんでした
Xcode 10からはこの不足部分が解消されています。 つまり、Xcodeからでも同じiPhone Simulatorに対して並列でテストを実行できるようになりました。
これはXcodeを使って開発している際においても、テストの実行時間を削減できるようになったということです。
Execute parallel on Simulator
この機能は「対象としたスキームに含まれるテストコードに対して、同じiOSのシミュレーターで並列実行させるかどうか」の設定になります。
デフォルトでこの設定は無効になっていますが、有効にすると1つの親からクローンのSimulatorを作り、並列にテストを実行してくれます。
xcodebuildコマンドのオプションとしては以下が追加されています。
xcodebuild -help
をすると他にも増えていることがわかります。
- -parallel-testing-enabled YES|NO
- 並列実行をするかどうか(Xcodeの設定を上書きします)
- -parallel-testing-worker-count NUMBER
- 並列数は実行する環境のスペックに依存して決めるのが良いです
この並列時の実行は、Androidで利用されるEspressoのtest shardingとは異なり、テストクラス単位で勝手にわりふられます。
並列化によりテストの実行時間は短縮されます。 ただし、この並列化をおこなうことにおいてよくあるケースとして、1つの実行時間が長いクラスがあるとその実行時間にひっぱられる形となるということです。
例えば、以下の図のようにクラスAからEまで5クラスあったとします。 クラスAの実行時間が長いと、並列数を増やしても最終的にAの実行時間がトータルの実行時間になってしまいます。
このような状況のときはどうしたら良いのでしょうか? よくいわれる対処法は、実行時間が長いクラスを分割することです。
今回の例ですと、クラスAをクラスA1とクラスA2なりに分割するということです。
これにより、トータルの実行時間は短くなります。
「WWDC 2018」では、必要になったら実行時間が長いクラスを分割しましょうという話がありましたが、必要なタイミングで分割しやすい形になっているとは限りません。
そのため、テストコードを最初から分解しやすい形にしておくことが求められます。
つまり、テストコードでは、「前処理」「実行箇所」「検証」「後処理」といったものをちゃんと分けて実装しておくことが大事です。
XCTestにおいてもsetUp()
とtearDown()
があるので、十分に活用することが大事です。
そして、「Four-phase test」や「AAA」といったものを知って実践するのが良いでしょう。
テストのランダム実行でも同様ですが、より自動テストを適切に実装する必要性が問われるようになってきているとも言えます。
なお、fastlaneでも利用されているテスト結果をファイル形式などで出力してくれるxcprettyはこの並列実行への対応がおこなわれていません。 そのため、fastlaneのscanなどを利用している場合は、テスト結果の出力は不完全な状態ではあります。
並列実行のその次のステップへ
今回のような並列実行ができるようになったというのは次のステップがいろいろと考えられるようになったとも言えます。
「WWDC 2016」で発表されましたが、xcodebuildコマンドでクラスを指定してテストコードを実行するオプション(only-testing)が追加されています。
このようなオプションと組み合わせることにより、さらなるテストの実行時間の最適化はできるようになります。 しかし、現状の仕組みを用いて自身でこの最適化をおこなうのは非常に高コストではあります。
例えば、並列実行に関してはCircleCIではparallelismというkeyを設定することにより並列実行をしてくれるというものがあります。 詳細については以下のページをチェックしてもらえればと思います。
また、Firebase Test Labというデバイスファームを用いて、テストを並列化するFlankというものもあります。
このFlankがBitriseから利用できるようになるという話もあります。
このように、今後はCIサービス側とその先のサービスで並列化をよりサポートをしてくれるようになってくるかもしれません。
おわりに
今回紹介したようにXcode 10からは、xcodebuildコマンドからの実行だけでなくXcodeからの実行においても非常にいろいろと便利になってきています。
自動テストにおける土台は少しずつではありますが進化してきています。
ここ数年の進化を駆使すればいろいろとできることは増えています。 その結果として、いろいろなサービスも生まれてきています。
しかし、まだまだその手の知見が共有できていない状況といえます。 ぜひとも、本記事などを参考に触っていただき得られた知見を世に公開してもらえると嬉しい限りです。