続・そろそろPower Assertについてひとこと言っておくか
3年前にこんな記事をあげました。
3行でまとめると、
- Power Assertはユニットテストのためにほしかったものではない
- 欲しいのは結果の差分
- 誰か作って!
というエントリでした。 そしたら id:pocketberserker が作ってくれました!
PowerAssertより強そうな名前でいい感じです。
Power Assertは時代遅れ、今はMuscle Assertだ!的な話かな?
— 裸のWPF/MVVMを書く男(マン) (@gab_km) 2016年6月1日
MuscleAssertの使い方
このライブラリは、PersimmonというF#用のテスティングフレームワークを拡張するライブラリとして作られています。 ただ、ざっくり概要をつかむだけであればどちらも知らなくても問題ありません。 このライブラリでできることはほぼ1つだけです。
open Persimmon open Persimmon.Syntax.UseTestNameByReflection open Persimmon.MuscleAssert let add x y = x + y let ``add 2 3が5を返す`` () = test { do! add 2 3 === 5 }
以上。簡単。 これを実行しても成功してしまって面白みがないので、わざと間違ってみましょう。
open Persimmon open Persimmon.Syntax.UseTestNameByReflection open Persimmon.MuscleAssert let add x y = x + x // ミス! let ``add 2 3が5を返す`` () = test { do! add 2 3 === 5 }
これをPersimmon.Consoleで実行すると、
Assertion Violated: add 2 3が5を返す 1. . left 4 right 5
こんなエラーが出てきました。 普通ですね。
では、例えばこんなJSONがあったとしましょう。
{"widget": { "debug": "on", "window": { "title": "Sample Konfabulator Widget", "name": "main_window", "width": 500, "height": 500 }, "image": { "src": "Images/Sun.png", "name": "sun1", "hOffset": 250, "vOffset": 250, "alignment": "center" }, "text": { "data": "Click Here", "size": 36, "style": "bold", "name": "text1", "hOffset": 250, "vOffset": 100, "alignment": "center", "onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;" } }}
これを読み込む関数を定義したとして、その関数をテストしたいですよね。
let expected = let ``JSONが読み込める`` () = test { do! read json === expected }
read
関数の実装にミスがあり、text
の vOffset
に hOffset
の値を使ってしまったとしましょう。
このテストを実行すると、下記のようなエラーメッセージが表示されます。
Assertion Violated: JSONが読み込める 1. .text.vOffset left 250 right 100
text
の vOffset
の値が左は 250
だったけど、右は 100
だった、ということが一目瞭然です。
MuscleAssert VS PowerAssert
MuscleAssertとPowerAssertの目的ははっきりと分かれています。 MuscleAssertが最初からテスティングフレームワークのアサーションを書くために特化しているのに対して、PowerAssertは(テストではなく)表明に使うことを前提にデザインされています。
表明手段
表明手段としてのPowerAssertはとても便利です。
言語内蔵の assert
は、条件式が false
の場合に何やらメッセージを出しますが、「どこで表明が false
と評価された」くらいの情報しか持っていません。
メッセージをカスタマイズすることはできますが、文字列で指定する必要があるため「どうなったか」を埋め込むのは大変です。
PowerAssertは、言語内蔵の assert
をそのままに表示されるメッセージをリッチにしてくれます。
表明として埋め込んだ式の「部分式の値」がメッセージとして表示されるため、「どの式の評価値が想定と違うのか」を調べるための情報をコーディングのコストを払わずに得られるようになるのです。
対してMuscleAssertはそもそも、Persimmon.MuscleAssertはPersimmon用のライブラリとして作られているため、Persimmonに依存しており単体で使えるものではありません。 表明に使えたとしても、MuscleAssertは式全体の評価結果の差分を出すため、ほしい情報である「どの式の評価値が想定と違うのか」を調べるための情報はそこに乗っていないでしょう。
表明手段としては、PowerAssertの圧勝です。
ユニットテスト用アサーション
しかし、MuscleAssertがやりたかったのは表明ではありません。 ユニットテストのアサーションとして使いたかったのです。
MuscleAssertが例えばJSONのようなネストした構造に対するテストに強そうだ、というのは先ほど紹介した例で分かると思います。 XMLやJSONやYAMLは当然として、そもそもクラス自体が何かを内部に持っているネスト構造をしているため、ネストした構造をそのまま比較してもわかりやすいメッセージが出力されるMuscleAssertは便利です。
対してPowerAssertはこの例には貧弱です。
let ``JSONが読み込める`` () = test { do! read json === expected }
このテストが失敗するとして、PowerAssertで表示されるのは
json
変数の中身read json
の結果expected
の中身read json === expected
がfalse
になったということ
ですかね。 どれもドバドバと大量の出力をするわりに、本当に欲しい「どこがどう違うのか?」という情報はそこから得るのは容易ではありません。 diffツールを使って外部でdiffとるとかしたことある人も多いんじゃないでしょうか?
そもそも、テストで actual
側に部分式が出てうれしいほど何かを書くことって多いのか?というのも疑問です。
このテストのように、多くのテストでは期待値との一点比較ができればいいのではないでしょうか?
ちなみに、MuscleAssertでは一度に複数の箇所の間違いを出してくれますので、小さいテストをまとめるのも容易です。
1. .image.hOffset left 500 right 250 .image.vOffset left 500 right 250 .text.vOffset left 250 right 100 .text.alignment left centre right center @@ -1,6 +1,6 @@ cent -re +er
MuscleAssertの弱点
MuscleAssertの弱点は、一点比較しかできないところです。 そのため、浮動小数点数を含むデータ構造を、浮動小数点数の一致範囲を指定して比較、ということは現状ではできません。 また、大小比較などもサポートしていません。
現状でこれらをテストしたい場合は、MuscleAssertを使わずにテストするしかありません。 今のところ、これで困ったことはありません(そういうテストが必要なドメインで仕事をしていない)。
まとめ
まとめも3行で。
- MuscleAssert便利
- テストのためのアサーションライブラリとしてはPowerAssertよりも便利
- 弱点はある。でも自分が困っていないから放置
みなさんも自分が使っている言語でMuscleAssertを実装してみてはいかがでしょう?便利ですよ。