Persimmonはユニットテストを分散実行する夢をみるか?
- みるかもしれない。
どういうこと?
かいつまんで問題を説明すると
- PersimmonというF#用テスティングフレームワークが存在する
- PersimmonでもRRRSpecやtest_queueみたいに分散実行したいね?
- Persimmonでのテスト実行の入力はdll
- 単純に転送するだけだとdllがロードできずに死ぬ
- どうしよう
- 案1. テストオブジェクトをシリアライズする?
- そもそもテストという"関数"をシリアライズできるのか?
- 案2. 各実行サーバにプロジェクトコードをsyncしてコンパイル、一部実行
という話。
今回採用したもの: 案1
コードというか題材というか
https://github.com/persimmon-projects/Persimmon.Ripe/tree/fee25ceabae567cb21e110fe2df2b4f07b72286c
テストオブジェクトのシリアライズ、assemblyロード
別サーバでテストを実行するためには、テストオブジェクトに関連するassemblyをロードする必要がある。 仮にロードしないでテストを実行した場合は例外投げてテストに失敗する。
しかしどうやってロードするかね、という話の段階でVagabondの出番となる。
https://github.com/nessos/Vagabond
このライブラリはリモート環境でAssemblyをロードしたり色々できる優れものである。 また、通信することを想定してFsPicklerというシリアライザ*1を用いてオブジェクトをシリアライズする。
現時点でのPersimmn.Ripeは、Vagabondを使ってAssemblyやテストをRabbitMQ越しにやり取りするライブラリを提供している。
- 入力のdll群をロードしてテストオブジェクト一覧を取得する
- ここまでは通常のPersimmonと同じ
- テストオブジェクトに関連するAssemblyをVagabondで取得、RabbitMQに投げる
- サーバ側はテキトーに受け取ってAssemblyをロードする
- テストオブジェクトをシリアライズしてRabbitMQに投げる
- インターフェースだとキャストに失敗して原因もよくわからなかったので
TextWriter -> obj
でやり取りしてる… - TextWriterを渡す理由は実行したテスト名を出力したいから
- インターフェースだとキャストに失敗して原因もよくわからなかったので
- 各サーバがメッセージをconsumeしてテスト実行、結果をRabbitMQに投げる
- 失敗したらエラーをメッセージとしてなげる
- 全体統括者がテスト結果を収集して、全部取得できたら結果表示
- エラーだったら規定回数リトライ、それでもダメなら自分で実行するとかもやってる
この方式のキモ
- Vagabondが行っているAssemblyの解析とロード
- FsPicklerが行っている、IL Emitを前提としたシリアライズ
この二つのようなことができれば他の中間言語を持つやつでもわりとなんとかできそう。
課題
Vagabondが対応できる範囲でしか実行できない。 実際、RabbitMQではなくAzure Service Busに切り替えて試してみようとしたところ、VagabondがAssemblyをロードできず死ぬという現象が発生した。
案2を採用しなかった理由
- コンパイルする環境整えるのつらくない?
- 全体統括者がWorkerとなるサーバ群を知らないといけないとかそこそこ面倒
- プロジェクトファイルのsyncだけでリソースもってかれそう
今後の予定とか
- VagabondでロードできるAssemblyを増やせないか試す
- AMQP以外のものにも対応させる
- 例えばアクターとか
- ちょっと気になっているRubyのtest_queueさんについて調べる
- Scalaで似たようなことができないか考える
*1:こっちもnessos製