NaturalSpecでSeq確認NG時のメッセージをわかりやすくする

これは F# Advent Calendar 2012 の22日目のエントリです。
21日目は@jsakamotoさんのF# スクリプトで GanttProject の .gan ファイルを加工するです。

まだ空いている日があったので、直前の参加表明、さらにとても小ネタで恐縮なのですが参加させて頂きます。


NaturalSpecでSeq、Listなどの内容を検証する場合に、

  Given x
    |> When function
    |> It should contain_all_elements_from expected
    |> It should contain_no_other_elements_than expected
    |> Verify

とかやると思います。
(その節は@gab_kmさんに助けていただきました。ありがとうございました。m(_ _)m)

その時にちょっと困ったことがあったのでそれをネタにしたいと思います。


まず、1から3までのリストの確認をしてみます。

パラメータをそのまま返す

let returnThrough seq = seq

という関数を用意して、テスト結果のメッセージを見ていきます。

成功するテストケースでは、

[<Scenario>]
let リスト確認3までOK () =
  let input    = [1;2;3]
  let expected = [1;2;3]
  Given input
    |> When returnThrough
    |> It should contain_all_elements_from expected
    |> It should contain_no_other_elements_than expected
    |> Verify

Scenario: リスト確認3までOK
  - Given [1; 2; 3]
     - When 
      => It should contain all elements from [1; 2; 3]
      => It should contain no other elements than [1; 2; 3]
  ==> Result is: [1; 2; 3]
  ==> OK
  ==> Time: 0.4129s

となります。

失敗するケースだと、

[<Scenario>]
let リスト確認3までNG_実行結果に3がない () =
  let input    = [1;2;2]
  let expected = [1;2;3]
  Given input
    |> When returnThrough
    |> It should contain_all_elements_from expected
    |> It should contain_no_other_elements_than expected
    |> Verify


Scenario: リスト確認3までNG 実行結果に3がない
  - Given [1; 2; 2]
     - When 
      => It should contain all elements from [1; 2; 3]
  Expected: True
  But was:  False


こんな感じのメッセージが出ます。
これくらいであれば、(ここではGivenに検証対象のリストがあるので)ぱっと見で何が違うかわかります。
でも、要素が増えて

[<Scenario>]
let リスト確認30までNG_実行結果に25がない () =
  let input    = [1;2;3;4;5;6;7;8;9;10;11;12;13;14;15;16;17;18;19;20;21;22;23;24;24;26;27;28;29;30]
  let expected = [1;2;3;4;5;6;7;8;9;10;11;12;13;14;15;16;17;18;19;20;21;22;23;24;25;26;27;28;29;30]
  Given input
    |> When returnThrough
    |> It should contain_all_elements_from expected
    |> It should contain_no_other_elements_than expected
    |> Verify

Scenario: リスト確認30までNG 実行結果に25がない
  - Given [1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 11; 12; 13; 14; 15; 16; 17; 18; 19; 20...
     - When 
      => It should contain all elements from [1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 11; 12; 13; 14; 15; 16; 17; 18; 19; 20...
  Expected: True
  But was:  False

とかなると、リストの全要素が表示されないので、ちょっとこれではパッと見で何が違うのかわからない。
また実際はGivenがそのまま実行結果にはならないので、要素が少なくてもわからないと思います。
違うところはここだよ!というメッセージがほしいですね。


なのでcontain_all_elements_fromを書き換えてしまいましょう。
例えば同一モジュール内で、以下の関数を再定義してしまえば、

let contain_all_elements_from x y =
    printMethod x
    let actualNotContained = x |> Seq.filter (fun element -> not (Seq.exists ((=) element) y))
    let foundAll = Seq.isEmpty(actualNotContained)
    if not foundAll then
        toSpec ("/actual is not contained:" + (prepareOutput actualNotContained))
    IsTrue,true,foundAll,y

テスト結果はこうなります。

Scenario: リスト確認30までNG 実行結果に25がない
  - Given [1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 11; 12; 13; 14; 15; 16; 17; 18; 19; 20...
     - When 
      => It should contain all elements from [1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 11; 12; 13; 14; 15; 16; 17; 18; 19; 20.../actual is not contained:seq [25]
  Expected: True
  But was:  False

これで一発で25が実行結果に存在しないことがわかります。

同様に、contain_no_other_elements_thanについても

let contain_no_other_elements_than x y =
    printMethod x
    let found = y |> Seq.forall (fun element -> Seq.exists ((=) element) x)
    let expectedNodContained = y |> Seq.filter (fun element -> not (Seq.exists ((=) element) x))
    let foundAll = Seq.isEmpty(expectedNodContained)
    if not foundAll then
        toSpec ("/expected is not contained:" + (prepareOutput expectedNodContained))
    IsTrue,true,foundAll,y

と定義してしまえば、

[<Scenario>]
let リスト確認30までNG_期待結果に25がない () =
  let input    = [1;2;3;4;5;6;7;8;9;10;11;12;13;14;15;16;17;18;19;20;21;22;23;24;25;26;27;28;29;30]
  let expected = [1;2;3;4;5;6;7;8;9;10;11;12;13;14;15;16;17;18;19;20;21;22;23;24;24;23;27;28;29;30]
  Given input
    |> When returnThrough
    |> It should contain_all_elements_from expected
    |> It should contain_no_other_elements_than expected
    |> Verify

Scenario: リスト確認30までNG 期待結果に25がない
  - Given [1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 11; 12; 13; 14; 15; 16; 17; 18; 19; 20...
     - When 
      => It should contain all elements from [1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 11; 12; 13; 14; 15; 16; 17; 18; 19; 20...
      => It should contain no other elements than [1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 11; 12; 13; 14; 15; 16; 17; 18; 19; 20.../expected is not contained:seq [25]
  Expected: True
  But was:  False

こんなかんじになりすぐに分かって便利ですね!
これはまさにF# advent calendar2日目のNaturalSpec 実践入門
で書かれている、テスト用DSL拡張の一例になりますでしょうか。
(ていうかみんなこういうこと普通にやってるからあまり話題にならないんだろうな、多分)


これ、案件が一段落してから書いたのでホントは絶賛開発中に自分が欲しかった。。。


次は、今のところ空き番?で、最終日24日は@nobuhisa_k さんです!