組合せや順列でテストケースを作ると便利

突発的な改修が続いたり、コードをリファクタリングする期間が取れなかったりすると、
「条件が複雑になる」→「変更を加えなければいけないから、他を触らずにとりあえず動くように一部分だけ変更する」→「更に複雑になる」→……
と、負のスパイラルが発生しがちです。
少しでも余裕が出来た隙に、さっとテストケースを作成するには、たとえばRubyだとArrayの組合せを作る機能が便利です。

# 例

## 対象コード

以下のような処理を何処からか取り出してきたとしましょう。

```komplex.rb
class Komplex
  # @param [Boolean] a,b,c,d
  # @param [Fixnum] num1,num2
  def foo(a,b,c,d,num1,num2)
    unless d
      unless !a && (b || num1 < 10) && (!b || !c && (d || a) && num2 == 0)
        :ok
      else
        :ng
      end
    else
      :ng
    end
  end
end
```

何が起きているかわからないし、リファクタリングしたくなります。

## 試験コード

さくっとMinitestを作成してしまいましょう

```komplex_test.rb
require 'minitest/unit'
require 'minitest/autorun'
require './komplex'

class KomplexTest < MiniTest::Unit::TestCase
  def setup
    @test = Komplex.new
  end

  def test_foo
    # pending
  end
end
```

## 試験コードのメタ生成

さて困りました。引数にある値が入った時に、`:ok` `:ng` のどちらが帰ってくるかパッとすぐには分かりません。
紙にベン図などを書くのも、条件が多すぎて困難です。
そんな時はArrayのメソッドである `repeated_permutation` や `product` を使うといいでしょう。

Komplex#fooのテストには、以下のような引数の組合せが良いなと考えたとします。

```math
\left(
  \begin{array}{c} true \\ false \end{array}
\right)
\left(
  \begin{array}{c} true \\ false \end{array}
\right)
\left(
  \begin{array}{c} true \\ false \end{array}
\right)
\left(
  \begin{array}{c} true \\ false \end{array}
\right)
\left(
  \begin{array}{c} 9 \\ 10 \\ 11 \end{array}
\right)
\left(
  \begin{array}{c} 0 \\ 1 \end{array}
\right)
```

2 * 2 * 2 * 2 * 3 * 2 で96通りの場合があるようです。やる気が削げますね。
`repeated_permutation` や `product` を駆使してみます

```rb
# 引数1-3の組合せ
bool_args = [true, false].repeated_permutation(3)
p bool_args.to_a
#=> [[true, true, true, true],
#   [true, true, true, false],
#   [true, true, false, true],
#   [true, true, false, false],
#       :
#   [false, false, false, true],
#   [false, false, false, false]]


# 引数1-3にさらに引数4,5をかけあわせる
test_args = bool_args.to_a.product([9,10,11], [0,1])
p test_args
#=> [[[true, true, true, true]], 9, 0],
#   [[true, true, true, true], 9, 1],
#   [[true, true, true, true], 10, 0],
#   [[true, true, true, true], 10, 1],
#   [[true, true, true, true], 11, 0],
#       :

# 整形
test_args.map!(&:flatten)
p test_args
#=> [[true, true, true, true, 9, 0],
#   [[true, true, true, true, 9, 1],
#       :
```

準備は整いました。先ほどのテストを書き換えます。

```komplex_test.rb
def test_foo
  # 準備いろいろ
  test_args.each do |args|
    test_eval = "@test.foo(#{args.join(",")})"
    puts "assert_equal #{eval(test_eval).inspect}, #{test_eval}"
  end
end
```

実行すると

```
# Running tests:

assert_equal :ng, @test.foo(true,true,true,true,9,0)
assert_equal :ng, @test.foo(true,true,true,true,9,1)
assert_equal :ng, @test.foo(true,true,true,true,10,0)
assert_equal :ng, @test.foo(true,true,true,true,10,1)
assert_equal :ng, @test.foo(true,true,true,true,11,0)
 :
```

という文字が延々と出力されました。
泥臭いですが、これを再度`test_foo`の中にコピペすれば網羅テストの完成です。

```komplex_test.rb
def test_foo
  assert_equal :ng, @test.foo(true,true,true,true,9,0)
  assert_equal :ng, @test.foo(true,true,true,true,9,1)
  assert_equal :ng, @test.foo(true,true,true,true,10,0)
  assert_equal :ng, @test.foo(true,true,true,true,10,1)
  assert_equal :ng, @test.foo(true,true,true,true,11,0)
     :
end
```

## まとめ

例に出したような歴史的経緯で複雑になっているコードは、ロジック自体の見直しも必要なことが多いと思われます。

網羅的なテストを最初に作成しておき

「第一引数がtrueのときは、この外の制約で第三引数は必ずtrueになる」->いくつかのテスト不要
「第二引数は現在では常にfalse」-> 第二引数がtrueのテストは不要

などと不要なテストケースを行削除し、本当に必要なテストコードだけ選別してからテスト→リファクタリングのサイクルに入ると良いのかなあと。

# 参考

* [Array - Ruby 2.1.0 リファレンスマニュアル](http://docs.ruby-lang.org/ja/2.1.0/class/Array.html)
  * [Array#product](http://docs.ruby-lang.org/ja/2.1.0/method/Array/i/product.html)
  * [Array#repeated_permutation](http://docs.ruby-lang.org/ja/2.1.0/method/Array/i/repeated_permutation.html)