Help us understand the problem. What is going on with this article?

テストメソッドの粒度について

More than 1 year has passed since last update.

はじめに

この記事は書籍「プロを目指す人のための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_2test_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を利用することで、上のようなテストを効率良く記述することができます。

詳しくは以下のリンクを参照してください。

次回予告

次回は配列を使ったキューとスタックについて説明します。

jnchito
SIer、社内SEを経て、ソニックガーデンに合流したプログラマ。 「プロを目指す人のためのRuby入門」の著者。 http://gihyo.jp/book/2017/978-4-7741-9397-7 および「Everyday Rails - RSpecによるRailsテスト入門」の翻訳者。 https://leanpub.com/everydayrailsrspec-jp
https://blog.jnito.com/
sonicgarden
「お客様に無駄遣いをさせない受託開発」と「習慣を変えるソフトウェアのサービス」に取り組んでいるソフトウェア企業
http://www.sonicgarden.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした