ククログ

株式会社クリアコード > ククログ > デバッグしやすいassert_equalの書き方

デバッグしやすいassert_equalの書き方

デバッグしやすいassert_equalの書き方とデバッグしにくいassert_equalの書き方があるのは知っていますか?1

デバッグしやすいassert_equalの書き方を2パターン紹介します。

まとめたassert_equal

まず、1つ目のよくみるデバッグしにくいassert_equalの書き方です。

def test_parse
  assert_equal(29, parse_integer("29"))   # (1)
  assert_equal(29, parse_integer("+29"))  # (2)
  assert_equal(-29, parse_integer("-29")) # (3)
end

これがデバッグしにくいのは、(1)が失敗したら(2)、(3)が実行されないからです。すべてのassert_equalが実行されて、どのassert_equalが失敗したかを確認することでバグの傾向を見つけやすくなります。

例えば…(1)だけが失敗するなら符号が無い場合の処理が怪しいでしょう。(3)だけが失敗するなら、マイナス記号に対応していないのかもしれません。全部失敗するなら根本的におかしいのでしょう。

このように、どのassert_equalが失敗したかがデバッグのヒントになります。よって、↑のような書き方はデバッグがしにくいassert_equalの書き方といえます。

デバッグしやすく書くと、例えば、こうなります。

def test_parse_no_sign
  assert_equal(29, parse_integer("29"))
end

def test_parse_plus_sign
  assert_equal(29, parse_integer("+29"))
end

def test_parse_mius_sign
  assert_equal(-29, parse_integer("-29"))
end

まとめないassert_equal

続いて、1つ目のよくみるデバッグしにくいassert_equalの書き方です。

def test_parse
  bob = MyJSONParser.parse('{"name": "bob", "age": 29}')
  assert_equal("bob", bob["name"])
  assert_equal(29, bob["age"])
end

1つ目の書き方のように複数のassert_equalが書いてあります。1つ目の書き方と同じように直すならこうなります。

def test_parse_string_value
  bob = MyJSONParser.parse('{"name": "bob", "age": 29}')
  assert_equal("bob", bob["name"])
end

def test_parse_numeric_value
  bob = MyJSONParser.parse('{"name": "bob", "age": 29}')
  assert_equal(29, bob["age"])
end

でも、本当にこれでよいでしょうか。この書き方では値の型だけを注目しています。値の型に注目するならば以下のように書いた方がよいでしょう。こうした方が余計なものがなくなり、より注目できています。

def test_parse_string_value
  bob = MyJSONParser.parse('{"name": "bob"}')
  assert_equal("bob", bob["name"])
end

def test_parse_numeric_value
  anonymous = MyJSONParser.parse('{"age": 29}')
  assert_equal(29, anonymous["age"])
end

もし、最初のコードが「複数のキーと値をパースできること」を確認したい場合はassert_equalを複数のテストに分割するのではなく、複数のassert_equalを1つのassert_equalにまとめるべきです。

def test_parse
  bob = MyJSONParser.parse('{"name": "bob", "age": 29}')
  assert_equal({"name" => "bob", "age" => 29},
               bob)
end

1つのassert_equalにまとめると、失敗する場合でも、一部の比較だけが実行されるのではなく、すべての比較が実行されます。そのため、"name"はパースに成功しているけど"age"は失敗している、というように、失敗の傾向がわかります。失敗の傾向がわかるとデバッグしやすくなるというのは前述の通りです。

おまけ: Hashのassert_equal結果が見づらい

ところで、Hashのassert_equalの結果は見づらいですよね。これは、RSpecでも一緒です。

{"age" => 30, "name" => "bob"}.should == {"name" => "bob", "age" => 29}

この結果は以下のようになります。

expected: {"name"=>"bob", "age"=>29}
     got: {"age"=>30, "name"=>"bob"} (using ==)
Diff:
@@ -1,2 +1,2 @@
-{"name"=>"bob", "age"=>29}
+{"age"=>30, "name"=>"bob"}

一見しただけではどこが違うのかわかりません。

しかし、最新のtest-unit 2は一味違います。

assert_equal({"name" => "bob", "age" => 29},
             {"age" => 30, "name" => "bob"})

この結果は以下のようになります。

<{"age"=>29, "name"=>"bob"}> expected but was
<{"age"=>30, "name"=>"bob"}>

diff:
? {"age"=>29, "name"=>"bob"}
?         30

違いがわかりやすいですね。

まとめ

デバッグしやすいassert_equalの書き方を2パターン紹介しました。今度から、assert_equalを書くときは、単に数を増やしたりするのではなく、どうすれば有用なassert_equalになるかを考えてみてください。assert_equalが失敗したときにいかにデバッグしやすいかがヒントになるはずです。

  1. assert_equalではなくて、should ==と読み替えてもよい。