はじめに
この記事は書籍「プロを目指す人のためのRuby入門」に掲載できなかったトピックを著者自らが紹介するアドベントカレンダーの6日目です。
本文に出てくる章番号や項番号は書籍の中で使われている番号です。
この記事ではテストメソッドの粒度に関する考察を書いていきます。
必要な前提知識
「プロを目指す人のためのRuby入門」の第3章まで読み終わっていること。
なお、本の中では1~6と15の場合しかテストしていませんが、初期の原稿ではもっと多くのパターンをテストしていました。
この記事は当時の内容に合わせて書かれています。
コラム:テストメソッドの粒度
今回作成したfizz_buzz
メソッドのテストは、test_fizz_buzz
というひとつのテストメソッドですべてのテストパターンをテストしています。
def test_fizz_buzz
assert_equal '1', fizz_buzz(1)
assert_equal '2', fizz_buzz(2)
assert_equal 'Fizz', fizz_buzz(3)
assert_equal '4', fizz_buzz(4)
assert_equal 'Buzz', fizz_buzz(5)
assert_equal 'Fizz', fizz_buzz(6)
assert_equal '7', fizz_buzz(7)
assert_equal '8', fizz_buzz(8)
assert_equal 'Fizz', fizz_buzz(9)
assert_equal 'Buzz', fizz_buzz(10)
assert_equal '11', fizz_buzz(11)
assert_equal 'Fizz', fizz_buzz(12)
assert_equal '13', fizz_buzz(13)
assert_equal '14', fizz_buzz(14)
assert_equal 'Fizz Buzz', fizz_buzz(15)
assert_equal '16', fizz_buzz(16)
assert_equal 'Fizz', fizz_buzz(21)
assert_equal 'Buzz', fizz_buzz(25)
assert_equal 'Fizz Buzz', fizz_buzz(60)
assert_equal '61', fizz_buzz(61)
assert_equal 'Fizz', fizz_buzz(164487)
assert_equal 'Buzz', fizz_buzz(200885)
assert_equal 'Fizz Buzz', fizz_buzz(759375)
assert_equal '1358209', fizz_buzz(1358209)
end
しかし、この方法にはデメリットがあります。アサーション(assert_equal
のような検証メソッド)が上から順番に実行されていくため、途中でアサーションが失敗するとそこから先のアサーションがパスするのか失敗するのか予想が付きません。
def test_fizz_buzz
# わざと1発目のアサーションを失敗させる
assert_equal '1', fizz_buzz(10)
# ここから先のアサーションは実行されないので、どれがパスしてどれが失敗するかわからない
assert_equal '2', fizz_buzz(2)
assert_equal 'Fizz', fizz_buzz(3)
assert_equal '4', fizz_buzz(4)
assert_equal 'Buzz', fizz_buzz(5)
assert_equal 'Fizz', fizz_buzz(6)
assert_equal '7', fizz_buzz(7)
assert_equal '8', fizz_buzz(8)
assert_equal 'Fizz', fizz_buzz(9)
assert_equal 'Buzz', fizz_buzz(10)
assert_equal '11', fizz_buzz(11)
assert_equal 'Fizz', fizz_buzz(12)
assert_equal '13', fizz_buzz(13)
assert_equal '14', fizz_buzz(14)
assert_equal 'Fizz Buzz', fizz_buzz(15)
assert_equal '16', fizz_buzz(16)
assert_equal 'Fizz', fizz_buzz(21)
assert_equal 'Buzz', fizz_buzz(25)
assert_equal 'Fizz Buzz', fizz_buzz(60)
assert_equal '61', fizz_buzz(61)
assert_equal 'Fizz', fizz_buzz(164487)
assert_equal 'Buzz', fizz_buzz(200885)
assert_equal 'Fizz Buzz', fizz_buzz(759375)
assert_equal '1358209', fizz_buzz(1358209)
end
理想的なテストコードの書き方は1つのテストメソッドにつき、アサーションを1つだけにすることです。
# 失敗するテスト
def test_fizz_buzz_with_1
assert_equal '1', fizz_buzz(10)
end
# 成功するテスト
def test_fizz_buzz_with_2
assert_equal '2', fizz_buzz(2)
end
# 成功するテスト
def test_fizz_buzz_with_3
assert_equal 'Fizz', fizz_buzz(3)
end
# 以下省略
こうすると各テストメソッドは独立しているので、test_fizz_buzz_with_1
が失敗してもtest_fizz_buzz_with_2
やtest_fizz_buzz_with_3
は実行されます。結果として、24件のテストパターンのうち、どれがパスしてどれが失敗するのかわかります。一方で、コード量が増えるのでテストを書いたり読んだりするのは大変になります。
「すべてまとめてテストする」「すべて独立させてテストする」の中間的なアプローチとして、特定の条件でグループ化する、という方法もあります。たとえばfizz_buzz
メソッドであれば、「3で割り切れる場合」「5で割り切れる場合」「15で割り切れる場合」「それ以外」というグループに分けられそうです。
# 成功するテスト
def test_fizz_buzz_3で割り切れる場合
assert_equal 'Fizz', fizz_buzz(3)
assert_equal 'Fizz', fizz_buzz(6)
assert_equal 'Fizz', fizz_buzz(9)
assert_equal 'Fizz', fizz_buzz(12)
assert_equal 'Fizz', fizz_buzz(21)
assert_equal 'Fizz', fizz_buzz(164487)
end
# 成功するテスト
def test_fizz_buzz_5で割り切れる場合
assert_equal 'Buzz', fizz_buzz(5)
assert_equal 'Buzz', fizz_buzz(10)
assert_equal 'Buzz', fizz_buzz(25)
assert_equal 'Buzz', fizz_buzz(200885)
end
# 成功するテスト
def test_fizz_buzz_15で割り切れる場合
assert_equal 'Fizz Buzz', fizz_buzz(15)
assert_equal 'Fizz Buzz', fizz_buzz(60)
assert_equal 'Fizz Buzz', fizz_buzz(759375)
end
# 失敗するテスト
def test_fizz_buzz_それ以外
assert_equal '1', fizz_buzz(10) # <= 失敗する
assert_equal '2', fizz_buzz(2)
assert_equal '4', fizz_buzz(4)
assert_equal '7', fizz_buzz(7)
assert_equal '8', fizz_buzz(8)
assert_equal '11', fizz_buzz(11)
assert_equal '13', fizz_buzz(13)
assert_equal '14', fizz_buzz(14)
assert_equal '16', fizz_buzz(16)
assert_equal '61', fizz_buzz(61)
assert_equal '1358209', fizz_buzz(1358209)
end
上のテストコードだと「それ以外」の場合は途中でテストが終わってしまいますが、その他のテストのメソッドは実行されます。
別の問題としてテストメソッドを細かく分けるとき困るのがメソッド名の付け方です。英語があまり得意でないプログラマだと、「3で割り切れる場合」という英語がぱっと出てこないかもしれません。テストメソッドの名前の付け方で時間を掛けてしまうのはちょっともったいないので、思い切って日本語を含めてしまう方法もアリです。上のテストコードではあえて日本語をテストメソッドに含めました。ですが、チームで開発したり、オープンソースのソフトウェアを開発したりする場合は、こういったメソッド名を許可するかどうか、周りの開発者と同意を取っておく必要があるでしょう。
本書(プロを目指す人のためのRuby入門)では「上から下に読める単純さ」を重視し、「テストメソッドを分けた方が明らかに読み書きしやすい」という場合を除いて、テストメソッドを分けないという方針をとります。ですが、テストメソッドを分けない場合のデメリットもあることを頭の片隅に置いておいてください。
参考:test-unitやRSpecの場合
test-unitやRSpecの場合は、データ駆動テスト機能やRSpec::Parameterizedというgemを利用することで、上のようなテストを効率良く記述することができます。
詳しくは以下のリンクを参照してください。
次回予告
次回は配列を使ったキューとスタックについて説明します。