Persimmonをdotnet testコマンドで実行するためのTestAdapter試作
https://www.nuget.org/packages/Persimmon.TestAdapter/
まだメジャーリリースには至っていないのですが、ひとまず動いたのでnugetにあげておきました。
これでようやくdotnet test
コマンドでテストができるようになったわけです。
kekyoさんが途中まで作成していたPersimmon.VisualStudio.TestExplorerを色々と書き換えた形になります。 PersimmonのVS拡張プロジェクトはある時期から停滞していたし(Persimmonに関わっている人はだいたい忙しい+私に力がなかった)、古い環境は正直サポートしなくていいかなと思ったので独断でリポジトリをTestAdapter用に作り変えてしまいました。
正式リリース以降の.NET Core SDKはVisual Studio Test Platformに従っているので、コア部分はTest Explorer向けVS拡張を流用できるわけです。
dotnet test
にはfilterオプションがあるのですが、もしかしたらこれがちゃんと動かないかもしれません。
なんでかというと、Persimmonにfilter機能がないからです!
全実行することを前提で作っていたからね、仕方がないね…そのうち頑張りたい。
というわけで記事自体は短いのですが…最後に、ある程度動く部分まで作ってくださったkekyoさんに感謝を!(これが書きたかった)
Persimmonのアサーション強化策たたき台
私が関わっているPersimmonというF#向けテスティングフレームワークはアサーションがとても貧弱です。
どのくらい貧弱かというとassertEquals
、assertNotEquals
、pass
、fail
、assertPred
、ignoreResult
しかない状況です。
これは(後付けですが)Coreは最小限のアサーションのみにとどめ、より高機能なアサーションは別ライブラリとして提供したいという思想に由来します。 昨今はJUnit5やJavaScript用のテスティングフレームワークで似たような雰囲気を感じます(私の気のせいかもしれませんが)。
さて、Persimmon.MuscleAssertというアサーション拡張が存在します。
Persimmon用アサーションライブラリMuscleAssertを作った - pocketberserkerの爆走
続・そろそろPower Assertについてひとこと言っておくか - ぐるぐる~
2つ目の記事でid:bleis-tiftさんが弱点を書いています。
MuscleAssertの弱点は、一点比較しかできないところです。 そのため、浮動小数点数を含むデータ構造を、浮動小数点数の一致範囲を指定して比較、ということは現状ではできません。 また、大小比較などもサポートしていません。
標準にはassertPred
という貧弱なものしかなく、MuscleAssertにも存在しない…これはおそらくそのうち不都合が生じます。
そんなわけで、なんでもいいからとりあえず何か作っておこうと思い立ってできたのがPersimmon.Unquoteです。
GitHub - persimmon-projects/Persimmon.Unquote
これはunquoteと呼ばれるF#向けライブラリのPersimmon用forkです。
Persimmon標準のアサーションを使うと以下のようになります。
assertPred (([3; 2; 1; 0] |> List.map ((+) 1)) = [1 + 3..1 + 0])
Assertion failed.
失敗したこと以外は何も得られないエラーメッセージですね(意図的にそうしているわけですが)。
Persimmon.Unquoteを使うと次のようになります。
open Persimmon.Unquote assertPred <@ ([3; 2; 1; 0] |> List.map ((+) 1)) = [1 + 3..1 + 0] @>
([3; 2; 1; 0] |> List.map ((+) 1)) = [1 + 3..1 + 0] [4; 3; 2; 1] = [4..1] [4; 3; 2; 1] = [] false
unquoteと同様の出力が得られます。 これが本当に欲しかったものかどうかはともかく、たたき台としては十分でしょう。
power assertが必要かどうか
power assertの定義が界隈で共通なのかイマイチわかりませんが、unquoteの出力は簡約結果を表示しているだけなので界隈でよく見られるpower assertではないでしょう。
じゃああのpower assert系の出力が本当に欲しかったものなのかと問われると…どうでしょうね。
あまりpower assertの恩恵にあやかれていない身としては判断が難しいです。
Assertion failed
よりは確実に良いことは確かでしょうけれども。
表示については今後のということにしておきます。
Persimmon 2.0.1とPersimmon.MuscleAssert 1.0.0をリリースしました
表題の通りですが、他にもDiff.Match.Patch 2.0.1とFSharp.Object.Diff 1.0.0もリリースしています。
NuGet Gallery | Persimmon 2.0.1
NuGet Gallery | FSharp.Object.Diff 1.0.0
NuGet Gallery | Diff.Match.Patch 2.0.1
NuGet Gallery | Persimmon.MuscleAssert 1.0.0
変更点はPCLや.NET Core対応、機能追加、bug fix、F# 4.1サポート、Visual Studio拡張のためのコードベース変更です。
注意点として、まだVisual Studioのテストエクスプローラー拡張や.NET Core用のAdapterはリリースできていないので実行できない点です。 あくまでライブラリが.NET Coreで実行できるようになったというところに悲しみが漂っていますね…。
本当はそれらも一緒にリリースできれば良かったのですが、謎の挙動によりテストが一部実行できないという状況のためライブラリ群と.NET Framework用コンソールランナーだけを先行リリースすることにしました。
バイナリ互換はありませんがソースコードレベルでの互換性は保っているつもりです。
残りの作業
- Visual Studioテストエクスプローラー用の拡張
- 一部テストが実行されない問題を解決する
- とはいえ私が必要としていないので永遠にリリースされない可能性もあり得る
- .NET Core用のアダプター
- モジュール変数として束縛したテストがnullになる現象を解決する(ちなみに関数やメソッド、プロパティとして定義したテストは動く)
- アセンブリはロードされているみたいでたちが悪い…
- Persimmon.DriedをPersimmon 2.xベースにする
- ついでにFsRandomの.NET Core対応をがんばる
- FsPicklerの.NET Core対応が当分先になりそうなのでMessagePackへの移行も考慮する(できればやりたくないけど)
- .NET Core SDK向けのテンプレートを作る
- リポジトリは用意してあるので中身をなんとかする
- Xamarin系のランナーを作る
- モックライブラリを作る
- Foqで問題ないけど、なんかこれじゃない感もある
- Power Assertを作る…?
- ドキュメントを増やす
- 増やせないフラグ
- プロジェクトサイトを作る
- リポジトリが増えたので
- ミューテーションテストフレームワーク構想
- fscxを使えばできそう
- テストの分散実行
- Persimmon.Ripeをもうちょっといい感じにしたい
- TCPソケットでプロセスの通信とか
明らかに1人で作業する分量ではない(確信)。 まぁ、手がまわらないなら切り捨てるか放り投げるだけなので問題ない(?)はず。
scala-zero-formatterのパフォーマンス改善やakka-httpサポートとか
https://github.com/pocketberserker/scala-zero-formatter/releases/tag/v0.7.0
表題の件以外にもlz4とzstdへの変換をサポートしてみたりしました。 この2つはあくまでとりあえず程度なので速度そんなにでないですが……。
速度改善の話
- 余分な型クラスを除去
- 一時期のバージョンでいれていたが今は無くても動く(もしかしたら昔も動いたのかもしれない)
- ついでに一部パッケージを統合
- unsafeモジュールでのStringのエンコード方法変更
- https://github.com/google/protobuf/blob/v3.2.0/java/core/src/main/java/com/google/protobuf/Utf8.java を借りることにした
- あまり速度に関係ないかもしれない、という噂はあるが今回は計測したら速くなったので
- case classの全フィールドのバイナリサイズがわかっている場合はmacroでバイナリサイズを算出する
- 最後のresize(arrayコピー)が減る
内部でprotobuf-javaのUnsafeを使っているので、そりゃScalaPBと速度で勝負できるよね…という。 とはいえデシリアライズがまだ若干遅いことがある問題?もあるのでなんとかできないか調査中。
akka-http
MarshallerとUnmarshallerをつかえば簡単にできそうだったので作ってみた、という。 JSON系ライブラリはspray-jsonは公式さぽーとだったり、他のライブラリも既にライブラリがあったりします。
msgpack用のライブラリもそのうち誰かがつくるのではないでしょうか?
lz4, zstd
最初に書きましたがまだお試しパッケージです。 そのため思ったほど速度がでないと思います(LZ4のデシリアライズはその特性上十分早いけど)。
あと、バイナリサイズを考慮して使うか決めないと圧縮前よりサイズが増える、なんてこともなくはない?
MessagePack-CSharp用の F# 拡張ライブラリを作った
MessagePack-CSharpがリリースされた話を読んですっごーいと感心していたら
F#拡張は @pocketberserker さんが作ってくれるでしょう(チラッ
— neuecc (@neuecc) 2017年3月13日
という通知が飛んできたので即興で作りました。
https://github.com/pocketberserker/MessagePack.FSharpExtensions
拡張でサポートする型
- list
- set
- map
- unit
- option
- 判別共用体
- FSharp.Coreのものはフィールド名ベースの変換のみサポート
- ResultやChoice
- FSharp.Coreのものはフィールド名ベースの変換のみサポート
- Async
- シリアライズで
Async.RunSynchronously
を使っているのでちょっと微妙かも?
- シリアライズで
このあたりに対応させています。
レコードは実は本家のライブラリ側でよしなにやってくれます。 つまり私が実装した拡張を導入しなくても苦労することなく速度の恩恵にあやかれます。
互換
判別共用体に関しては一応程度ですがC#側のUnionと相互変換できます。
- Tagプロパティでケース判定(0, 1, 2…)
- MessagePackObject属性が付いている場合はシーケンシャルなIntKeyを勝手に付与してフィールドをシリアライズ
- 属性が付いていないか、プロパティ名を使う指定になっていればフィールド名を使う
例えばF# 4.1から導入されたResultの具体例Result<int, string>
をstructで表すとこんな感じになるはずです。
[<Union(0, typeof<CsOk>)>] [<Union(1, typeof<CsError>)>] type CsResult = interface end and [<Struct; MessagePackObject(true)>]CsOk(resultValue: int) = member __.ResultValue = resultValue interface CsResult and [<Struct; MessagePackObject(true)>]CsError(errorValue: string) = member __.ErrorValue = errorValue interface CsResult
型パラメータはさすがにどうにもならない気がしています(ほんとか?)
ところで、他の言語で生成されたバイナリを扱ったりその逆パターンを考えるとプロパティ名を小文字にするオプションが欲しい気もするがどうなのでしょう? そういうときにMessagePackを採用することってあまりないから必要ない可能性も高いわけですが、はてさて。
がんばったところ
判別共用体のデシリアライズが(現行の)ZeroFormatter.FSharpExtensionsのものより洗練されています(たぶん)。 MessagePack-CSharpの知見とか、改めてFSharp.CoreのILを眺めた関係でBox化が挟まらなくなったことが大きいですね。
他の部分はそんなに頑張らなかったわけですが、MessagePack-CSharpのAPIがよくできていているので頑張らなくてもなんとかなる、というのが正しいです。
いやほんと素晴らしい、さすがの一言です!
今後
FsPicklerの真似事をするかどうか悩みつつ、要望があれば随時対応かなと考えています。
パフォーマンス調査結果はそのうち掲載します。
判別共用体のUnionCaseにAttributeを付与する
必要に迫られたので調べた。
open System // AttributeTargets.Property以外はエラー [<AttributeUsage(AttributeTargets.Property, Inherited = false); Sealed>] type TestAttribute() = inherit Attribute() // fieldがない場合: // AクラスのB getプロパティにつく type A = | [<Test>] B // fieldがない場合: // CクラスのNewDメソッドにつく type C = | [<Test>] D of int
struct unionの場合も特に違いはなし。
AttributeTargets.Property
なのにメソッドかよーみたいな気持ちはありつつ、でもまぁそこしかないかみたいな。