CIや実機でUnityのユニットテストを実行してSlackに通知するなどする
- 2019-05-06
前回(?)CircleCIでUnityをテスト/ビルドする、或いは.unitypackageを作るまででは、ユニットテストに関する部分がうまく行ってなくて放置でした。放置でいっかな、と思ってたんですが、改めてユニットテストをCIでがっつり実行したい、というかIL2CPPのテストをがっつしやりたい。という切実な要望が私の中で発生したので(N回目)、改めて取り組んでみました。
さて、オフィシャルな(?)ユニットテストのコマンドラインの実行の口は、Writing and executing tests in Unity Test Runnerの最後の方のRunning from the command lineの節に書いてありました(コマンドライン引数のほうのマニュアルにはリンクすら張ってなかったので気づかなかった……!)。つまり、こんなふうにやればいい、と。
Unity.exe -runTests -testResults C:\temp\results.xml -testPlatform StandaloneWindows64
そうすると、テストが失敗しても正常終了して(?) results.xml に結果が入ってるからそっち見ればOK、と。んー、いや、何か違うような。「Run all in player」で出てくるGUI画面も意味不明だし、Editor上のTest Runnerはいい感じなのだけれど、ビルドしてのテストだとイマイチ感がめっちゃ否めない。
と、いうわけで、なんとなく見えてきたのは、テストはUnity Test Runnerでそのまま書きたいしエディタ上でPlay Modeテストもしたい。それをそのままCIや実機でテストできるように、表示やパイプラインだけをいい具合に処理するビルドを作る何かを用意すればいいんじゃないか、と。
RuntimeUnitTestToolkit v2
ちょうどUnity Test Runnerがイマイチだった頃に作った俺々テストフレームワークがありました。ので、それを元にして、Unity Test RunnerのCLI/GUIのフロントエンドとして機能するようにリニューアルしました。コード的には全面書き換えですね……!
Unity Test RunnerのPlayModeで動くテストがあれば、それだけで他に何もする必要はありません。例えばこんなやつがあるとして
メニューのほうで適当にターゲットフレームワークとかIL2CPPがどうのとかを設定してもらって
BuildUnitTestを押すと、こんなような結果が得られます。
比較的ヒューマンリーダブルなログ!WindowsでもIL2CPPビルドができるようになったのがとっても捗るところで、検証用の小さめプロジェクトなら1分あればコード編集からチェックまで行けるので、リフレクションのキワイ部分をごりごり突いてもなんとかなる!昔のiOSでビルドして動かしてをやってたのは本当に死ぬほど辛かった……。
これはHeadless(CUI)でビルドしたものですが、GUIでのビルドも可能です。
イケてる画面かどうかでは微妙ですが、機能的には十二分です。Headlessだと上から下まで全部のテストを実行しちゃいますが、GUIだとピンポイントで実行するテストを選べるので(ただしメソッド単位ではなくクラス単位)、テストプロジェクトが大きくなっている場合はこっちのほうが便利ですね。
さて、Headlessでビルドしたものは、もちろんCIでそのまま実行できます。
これはNGが出ている例ですが、ちゃんと真っ赤にCIのパイプラインが止まるようになってます。止まればもちろんCIの通知設定で、Slackでもなんでもどこにでもサクッと飛ばせます。実に正しい普通で普遍なやり方でいいじゃないですか。はい。というわけでやりたかったことが完璧にできてるのでめでたしめでたし。
Linux ContainerとUnity
相変わらずCircleCIで色々トライしているのですが、Linuxコンテナ + Unityでの限界、というかUnityのLinux対応が後手に回ってる影響をくらってビミョーという現実がやっと見えてきました。まず、そもそもにLinux + IL2CPPはまだサポートされてないので、CI上でIL2CPPビルドしたものを実行してテスト、みたいなのはその時点でできない。残念。しゃーないのでWindows + IL2CPPビルドを作って、実行だけ手元でやるのでもいっか、と思ったらそもそもLinuxでIL2CPPビルドができない。なるほど、そりゃそうか、って気もしますが悲しみはある。
と、いうわけで、コンテナベースでやるとどうしてもLinuxの上でのパターンが中心になってしまうので、Unityだと結構厳しいところはありますよねえ、という。
さて、CircleCIの場合は(有料プランでは)Mac VMも使えるので、多少コンフィグの書き方も変わってきますが(マシンセットアップ部分が面倒くさくなる!)、動かせなくもないんちゃうんちゃうんといったところです。或いはAzure DevOpsなどを使えばWindowsマシンが使えるので、こちらもUnityのインストールなどのセットアップは必要ですが、安心感はありますね。どちらにせよWindowsでしかビルドできないもの(Hololensとか)もあるので、ちょっとちゃんと考えてみるのはいいのかなあ、と思ってます。
何れにせよ、VMでやるんだったらそりゃ普通にできますよね、という当たり前の結論に戻ってくるのが世の中きびすぃ。とりあえず私的にはIL2CPPビルドが実行できればいいので、Linux + IL2CPP対応をどうかどうか……。
RandomFixtureKit
ユニットテスト用にもう一個、RandomFixtureKitというライブラリを作りました。こちらは .NET Core用とUnity用の両対応です。
なにかというと、オブジェクトにランダムで適当な値を自動で詰め込むという代物です。当然リフレクションの塊で、これのIL2CPP対応に、先のRuntimeUnitTestToolkitが役に立ちました。
APIも単純でFixtureFactory.Createで取り出すだけ。
// get single value
var value = FixtureFactory.Create<Foo>();
// get array
var values = FixtureFactory.CreateMany<Bar>();
// get temporal value(you can use this values to use invoke target method)
var (x, y, z) = FixtureFactory.Create<(int, string, short)>();
テスト書いていてダミーのデータを延々と書くの面倒くせー、という局面はめっちゃあって、別に賢い名前なんて必要なくて(例えばAddressにはそれっぽい住所、Nameにはそれっぽい人名を入れてくれるとか)、全然ランダム英数でもいいから詰めてくれればそれでいいの!というところにピッタリはまります。
実用的には、私はシリアライザの入れ替えとか(なぜか)よくやるんですが、旧シリアライザと新シリアライザで互換性なくて壊れたりしないように、相互に値を詰めたりとかして、同一の結果が得られることを確認したりします。そのときに、dllをなめて対象になる数百の型を取って、RandomFixtureKitを使って、適当な値を詰めた上で、一致を比較するユニットテストを用意するとかやったりします。
面白い機能としては、ランダムな値ではなくて、エッジケースになり得る値だけを詰めるモードを用意しています。
たとえばintだったらint.MinValue, MaxValue, 0, -1, 1を。コレクションだったらnull, 長さ0, 長さ1, 長さ9の中からランダムで詰める、といったものですね。
こういうキワいデータが入ったときにー、みたいなことは想定しなきゃいけないし、テストも書いておかなきゃなのは分かってるけれど、毎回データ変えて流すのクソ面倒くさいんですよね(私はシリアライザを(なぜか)よく書くので、本当にこういうデータをいっぱい用意する必要が実際ある)。ので、CreateManyで1000個ぐらい作って流し込んでチェックすれば、多少はケースが埋まった状態になるでしょうというあれそれです。使ってみると意外と便利ですよ。
ところで
ゴールデンウィークの最終日なのですが、ほとんど何もやってない!始まる前は、MessagePack-CSharpやMagicOnionのタスクを潰しつつ、Pure C#のHTTP/2 Clientを作ってMagicOnionを強化するぜ、とか息巻いていたのですが全然できてない。副産物というか横道にそれたユニットテスト関連を仕上げて終わりとか、なんと虚しい……。
できなかった理由の半分はSwitchでCelesteを遊び始めたらめちゃくちゃハマって延々とやり続けちゃったせいなのですが、まぁそれはそれで面白いゲームをたっぷり楽しめたということで有意義なのでよしということにしておきます。
MagicOnionは6月4日に勉強会をやります。というわけで、やる気もかなりあるし、アップデートネタも溜まっているんですが、実際にアップデートはできてないので(Issueのヘンジはちゃんとやってます!)、GWでガッと手を入れておきたかったんですが、うーん、まぁ明けてからやりまうす。色々良い感じになっていると思います。いやほんと。