QuickCheck on RSpec

CIで何回も回すから味の出てくるテストケースを考えてみる にも書いたように、同値に区分されるテストデータの集合に対してテストを実施する場合、ランダムな値でテストデータのバリエーションを増やすことは現実的な選択肢としてありと思う。また、全網羅が難しいテスト対象の領域に対してこのようにランダムな値を適用することは現実的な選択肢になる。
※ランダム性は議論の範囲外

ここでは、以前から知られているQuickCheckを以下でRubyのコードを使ってためしていく。
また、先日書いた RSpecによるテストコードでパラメタライズドテストを記述していく と少し比較もしてみる。

Rubyだと、QuickCheckには以下のgemが個人的には良さそうだと感じた。
ただ、ある中身は実際にはrandを使ってたりと、比較的容易に実装可能なので、必要な箇所だけ独自で実装するのも良いかもしれない。
https://github.com/hayeah/rantly

このQuickCheckの目的自体は、先に書いた通り、ある集合領域に対するランダムな値を生成し、それをテストデータをしてテスト対象に与え、テスト対象を検査すること。ランダムに選択した値を与えることは、昔から経験則的に有効だということは知られている。

コードを実装しているときは、多くの場合、テストの設計まで頭が回らない。また、テストが書かれた場合も”通るべくして通る”テストしか書かれないことがおおい。そのため、例えばQuickCheckのようにランダムな値を与えると、CIを回しているうちに不具合を発見できる可能性もある。なお、私の経験上からも、不具合は境界値に含まれやすいことがおおい。

以下、QuickCheckの例。

テスト対象のコード

class Calculator
  def self.calculator arithmetic, num1, num2
    case arithmetic
    when 'plus'
      num1 + num2
    when 'sub'
      num1 - num2
    end
  end
end

Rantlyを使ったRSpec上でのQuickCheck。

require 'rantly'

## QuickCheck with Rantly
describe 'example' do
  let(:num_int1) { Rantly { integer } }
  let(:num_int2) { Rantly { integer } }
  let(:num_int3) { num_int1 + num_int2 }

  it 'num1 plus num2 is answer' do
    expect(Calculator.calculator('plus', num_int1, num_int2)).to eq num_int3
  end
end

パラメタライズドテストは、同一のテスト対象のコードに、振る舞いの変化するテストデータの集合から代表的なデータを選択・与え、それぞれの振る舞いを確認するときに有効な方法といえる。これにより、テスト対象がどのように振る舞うか、ということをSpecification By Exampleのようにテストを読んで理解することもできる。

QuickCheckのような形は、あるテスト対象のコードにいかなるテストデータを与えても、テストデータから一意に定まる結果しかかえってこないが全網羅するのが困難な場合なんかに有効。このとき、ランダムな値を与えていることは、テスト対象のコードに任意の値を与えれば良い、というように理解でき、テスト対象のコードへの理解も深まる。

また、特別なライブラリを使わず、特定の範囲内でランダムな文字列を生成するコードを以下に載せるので、興味のある方はのぞいてみてください。日本語も含んでいます。

Objective-Cに対してだと以下が良いかも。CocoaPodsに対応。
https://github.com/yaakaito/NLTQuickCheck

Javaに関しては
https://github.com/pholser/junit-quickcheck
https://bitbucket.org/blob79/quickcheck/src/c8e12cc026fd6295966bf9ff5d20919961ff4149/README.md?at=default
付近が見つかるのですが、どうなのでしょう。

最後に付録として、Rantlyを使った乱数を以下に例としてのせておく。

> require 'rantly'
> Rantly.map(5) { integer }
=> [-277935692956708941,
 918210169069990250,
 406855258238108122,
 1482046018572340152,
 1059116810751674617]

> Rantly.each(5) { puts integer }
-1686306511197491276
464470401537633672
-924679469122264276
-878292278026558995
-9093519485269269

> Rantly.map(5) { string }
=> ["iXJCAk", ":\\WXG#", "h,Mh Rantly.map(5) { string:alpha }
=> ["mndwdu", "AZKhrC", "pLmkwK", "DKBmax", "BdctLP"]

> Rantly.map(5) { string:blank }
=> [" \t\t \t\t", "\t \t\t  ", "\t\t   \t", " \t\t   ", " \t\t \t "]

> Rantly.map(5) { string:cntrl }
=> ["\t\e\a\u0004\u001D\v",
 "\u001E\u0015\n\u0016\u0018\u0011",
 "\e\u001E\u000E\u0006\u007F\u0006",
 "\t\u000E\u0004\b\u001D\u0010",
 "\t\u0003\u0019\u0002\u0014\t"]

> Rantly.map(5) { string:digit }
=> ["034074", "529585", "423587", "673367", "085750"]

> Rantly.map(5) { string:graph }
=> ["qE_Xw\"", "xU>fo:", "|28v;k", "k>,QF$", "Zs3@~A"]

> Rantly.map(5) { string :lower }
=> ["lzgwsr", "vrphsn", "drrlle", "fyliyq", "yrvalf"]

> Rantly.map(5) { string :print }
=> ["S3$WL ", "`&i?M#", "2>3Vi^", "=l[6_T", "9}^**O"]

> Rantly.map(5) { string :punct }
=> ["@%)#(-", "(*)[!:", "/&%?;'", "{\"@\"?(", ".{*),."]

> Rantly.map(5) { string :space }
=> ["\f\f \r\r\n", "\n\r\t\r\f\v", "\f\f\n\r\v ", "\r\r\v\n\n\r", "\v\f\r \f\v"]

> Rantly.map(5) { string :upper }
=> ["VVUMSW", "CXVFOS", "WTVXNL", "EDZBWN", "CEVVYM"]

> Rantly.map(5) { string :xdigit }
=> ["DD5E9b", "006CBc", "8eF2F0", "E34F0e", "df6aA1"]

> Rantly.map(5) { string :ascii }
=> ["acv:11",
 "J?$\u0019\u000EE",
 "1;@\u0002(@",
 "1\u0011\u0003\u007FV\u0002",
 "2S\u0006\vZ`"]

> Rantly { freq :integer, :float, :string }
=> 0.26470712353632486

> Rantly { freq :integer, :float, :string }
=> ";qn66x"

> Rantly { freq :integer, :float, :string }
=> 1480295461981773153

> Rantly { sized(10) { string } }
=> "6O$RB-$fBv"

> Rantly { sized(10) { range(lo=1, hi=100 ) { integer } } }
=> 14

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中