Giter Club home page Giter Club logo

rantly's Introduction

Imperative Random Data Generator and Quickcheck

You can use Rant to generate random test data, and use its Test::Unit extension for property-based testing.

Rant is basically a recursive descent interpreter, each of its method returns a random value of some type (string, integer, float, etc.).

Its implementation has no alien mathematics inside. Completely side-effect-free-free.

Install


$ gem install hayeah-rant --source http://gems.github.com

$ irb
> gem 'rant'
> require 'rant'
> Rant.gen.value { [integer,float] }
=> [20991307, 0.025756845811823]
> Rant.gen.value { [integer,float]}
=> [-376856492, 0.452245765751706]

Data Generation

You can create random generators from the Rant class. Rant.gen is just returns a class instance of Rant.


> gen = Rant.new
> gen.value { [integer,float] }
=> [-163260081, 0.356075765934108]

Getting Random Data Values


Rant#map(n,limit=10)
  call the generator n times, and collect values
Rant#each(n,limit=10)
  call a random block n times
Rant#value(limit=10)
  call a random block once, and get its value.

To collect an array of random data,


# we want 5
> gen.map(5) { integer }
=> [-380638946, -29645239, 344840868, 308052180, -154360970]

To iterate over random data,


> gen.each(5) { puts integer }
296971291
504994512
-402790444
113152364
502842783
=> nil

To get one value of random data,


> gen.value { integer }
=> 278101042

The optional argument limit is used with generator guard. By default, if you want to generate n items, the generator tries at most n * 10 times.

This almost always succeeds,


> gen.map(5) { i = integer; guard i > 0; i }
=> [511765059, 250554234, 305947804, 127809156, 285960387]

This always fails,


> gen.map(10) { guard integer.is_a?(Float) }
Rant::TooManyTries: Exceed gen limit 100: 101 failed guards)

Random Generating Methods

The API is similiar to QuickCheck, but not exactly the same. In particular choose picks a random element from an array, and range picks a integer from an interval.

Simple Randomness


Rant#integer(n=nil)
  random positive or negative integer. Fixnum only.
Rant#range(lo,hi)
  random integer between lo and hi.
Rant#float
  random float
Rant#bool
  true or false
Rant#literal(value)
  No-op. returns value.
Rant#choose(*vals)
  Pick one value from among vals.

Meta Randomness

A rant generator is just a mini interpreter. It’s often useful to go meta,


Rant#call(gen)
  If gen is a Symbol, just do a method call with send.
  If gen is an Array, the first element of the array is the method name, the rest are args.
  If gen is a Proc, instance_eval it with the generator.

> gen.value { call(:integer) }
=> -240998958

> gen.value { call([:range,0,10]) }
=> 2

> gen.value { call(Proc.new { [integer] })}
=> [522807620]

The call method is useful to implement other abstractions (See next subsection).


Rant#branch(*args)
  Pick a random arg among args, and Rant#call it.

50-50 chance getting an integer or float,


> gen.value { branch :integer, :float }
=> 0.0489446702931332
> gen.value { branch :integer, :float }
=> 494934533

Frequencies


Rant#freq(*pairs)
  Takes a list of 2-tuples, the first of which is the weight, and the second a Rant#callable value, and returns a random value picked from the pairs. Follows the distribution pattern specified by the weights.

Twice as likely to get a float than integer. Never gets a ranged integer.


> gen.value { freq [1,:integer], [2,:float], [0,:range,0,10] }

If the “pair” is not an array, but just a symbol, freq assumes that the weight is 1.


# 50-50 between integer and float
> gen.value { freq :integer, :float }

If a “pair” is an Array, but the first element is not an Integer, freq assumes that it’s a Rant method-call with arguments, and the weight is one.


# 50-50 chance generating integer limited by 10, or by 20.
> gen.value { freq [:integer,10], [:integer 20] }

Sized Structure

A Rant generator keeps track of how large a datastructure it should generate with its size attribute.


Rant#size
 returns the current size
Rant#sized(n,&block)
 sets the size for the duration of recursive call of block. Block is instance_eval with the generator.

Rant provides two methods that depends on the size


Rant#array(size=default_size)
  returns a sized array consisted of elements by Rant#calling random branches.
Rant#string(char_class=:print)
  returns a sized random string, consisted of only chars from a char_class.

The avaiable char classes for strings are:


:alnum
:alpha
:blank
:cntrl
:digit
:graph
:lower
:print
:punct
:space
:upper
:xdigit
:ascii

# sized 10 array of integer or float
> gen.value { array(10) { branch(:integer,:float)}}
=> [417733046, -375385433, 0.967812380000118, 26478621, 0.888588160450082, 250944144, 305584916, -151858342, 0.308123867823313, 0.316824642414253]

If you set the size once, it applies to all subsequent recursive structures. Here’s a sized 10 array of sized 10 strings,


> gen.value { sized(10) { array(:string)} }
=> ["1c}C/,9I#}", "hpA/UWPJ\\j", "H'~ERtI`|]", "%OUaW\\%uQZ", "Z2QdY=G~G!", "H<o|<FARGQ", "g>ojnxGDT3", "]a:L[B>bhb", "_Kl=&{tH^<", "ly]Yfb?`6c"]

Or a sized 10 array of sized 5 strings,


> gen.value { array(10) { array(5) { string}}}
=> ["S\"jf ", "d\\F-$", "-_8pa", "IN0iF", "SxRV$", ".{kQ7", "6>;fo", "}.D8)", "P(tS'", "y0v/v"]

Rant#array actually just delegate to Rant#freq, so you can use freq pairs:


> gen.value { sized(10) {array [1,:integer],[2,:float] }}
=> [0.983334733158678, -418176338, 0.976947175363592, 0.703390570421286, -478680395, 5483631, 0.966944106783513, 110469205, 0.540859146793544, 0.521813810037025]

Property Testing

Rant extends Test::Unit for property testing. The extension is in its own module. So you need to require it.


require 'rant/check'

It defines,


Test::Unit::Assertions#property_of(&block)
  The block is used to generate random data with a generator. The method returns a Rant::Property instance, that has the method 'check'.

It’s like this, using the gem ‘shoulda’


# checks that integer only generates fixnum.
should "generate Fixnum only" do
   property_of  { integer }.check { |i| assert i.is_a?(Integer) }
end

The check block takes the generated data as its argument. One idiom I find useful is to include a parameter of the random data for the check argument. For example, if I want to check that Rant#array generates the right sized array, I could say,


should "generate right sized array" do
  property_of {
    len = integer
    [len,array(len){integer}]
  }.check { |(len,arr)|
    assert_equal len, arr.length
  }
end

That’s about it. Enjoy :)

Copyright

Copyright © 2009 Howard Yeh. See LICENSE for details.

rantly's People

Contributors

hayeah avatar

Stargazers

 avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.