読者です 読者をやめる 読者になる 読者になる

Tbpgr Blog

Ruby プログラマ tbpgr(てぃーびー) のブログ

Rubyで個別に具体的なテストケースを作成せずに自動テストをする

f:id:tbpg:20170515225335p:plain

記事タイトルをみて「なんのこと?」と思った方もいるかもしれませんが、
RSpecJUnit などに代表されるような example-based testing をせずに
property-based testing という方法でテストをする、というお話です。

property-based testing とは?

下記を参照ください

tbpgr.hatenablog.com

ランレングス圧縮(連長圧縮)をテスト

ランレングス圧縮を property-based testing でテストします。
テストは Ruby の property-based test 用のライブラリ rantly で行います。

ランレングス圧縮については下記を参照。

テストコード

require "rantly"
require 'rantly/rspec_extensions'

class RunLengthEncoding
  def self.encode(text)
    text.chars
        .chunk{ |c| c }
        .map{|e|e.last.size == 1 ? e.first : "#{e.first}#{e.last.size}" }
        .join
  end

  def self.decode(text)
    text.scan(/(.)(\d*)?/)
        .map{|e|
          e.last.empty? ? e : (e.first * e.last.to_i)
        }.join
  end
end

RSpec.describe 'example' do
  it do
    property_of {
      # 長さ50文字のランダムな英小文字文字列をテスト対象として生成する
      sized(50) {string(:lower)}
    # ランダムな値で5回テストを実行する
    }.check(5) { |i|
      encoded = RunLengthEncoding.encode(i)
      decoded = RunLengthEncoding.decode(encoded)
      print "encoded: #{encoded}", "\n"
      print "decoded: #{decoded}", "\n"

      # 入力した値とエンコードしたあとにデコードした値は同じになるはず
      expect(decoded).to eq(i)
    }
  end
end

テスト実行

encoded: xjtrchxkqkoyfaelwsmcmjwvbhzlgyaiqaykcybdyeshj2icph
decoded: xjtrchxkqkoyfaelwsmcmjwvbhzlgyaiqaykcybdyeshjjicph

.encoded: mktch2qwdkhgacsfvdbtnmeluriebvtn2jp2hgpxplpoydakmf
decoded: mktchhqwdkhgacsfvdbtnmeluriebvtnnjpphgpxplpoydakmf
encoded: jnzorbkyptkogiovymryqvnucsodibgbjsmkpfvpw2m2pduvab
decoded: jnzorbkyptkogiovymryqvnucsodibgbjsmkpfvpwwmmpduvab
encoded: zydqdbsgqcpzuylksumxjtkoy2kuswxkewsgbgawvx2ckihcqu
decoded: zydqdbsgqcpzuylksumxjtkoyykuswxkewsgbgawvxxckihcqu
encoded: kor2dicitzaoayfjyiatjmspw3htymtzlvgbtkpqvsrdx2tpb
decoded: korrdicitzaoayfjyiatjmspwwwhtymtzlvgbtkpqvsrdxxtpb

success: 5 tests
.

Finished in 0.00205 seconds (files took 0.08749 seconds to load)
1 example, 0 failures

まとめ

example-based testing の場合は個別のテストケースをハードコートする必要がありましたが、
property-based testing では入力データのルールだけ決めておけばそれを元にランダムにデータを生成してくれることが確認できました。
サンプルなので5回だけ実行していますが、実際は様々なケースが実行されるようにもっと大きな回数を指定することになるでしょう。

最近 property-based testing を知って、現実問題どのくらいのケースで適用できるのか把握しきれていないこともあり、
他の方の情報を見てみたい思いがあります。

現状の私の理解としては

  • property-based testing は example-based testing に比べて個別のケースをハードコートしないで良い分楽であり、ケース漏れしにくい
  • property-based testing を適用する場合、処理を実行した結果が何らかの性質をもっていることを assert できる必要がある

このような性質があるため、基本的に property-based testing が使える場面では example-based testing のほうが好ましいが、
すべてのケースに適用できるわけではない。
というような理解をしているのですが、実際のところどうなのかよくわかっていません。

知りたいのは

  • property-based testing にはどのようなメリットがあるのか?
  • property-based testing を利用すべき対象の明確な基準は何か?

です。

より詳しい人の解説に期待です。

補足

Rantly の様々な機能については Qiita に多数の記事をまとめてあります。