This tutorial is written in a simple markup language RedCloth3. This file is also dawnloadable from rushcheck.txt and also in the distribution.
There are two ways to install RushCheck.
You can install RushCheck easily with rubygems.
% sudo gem install rushcheck
Done!
Instead, if you don’t have rubygems, or like to install from source codes, then you can follow the steps.
% sudo ruby setup.rb
See also the following;
% setup.rb help
Our darcs repository is provided for developer RushCheck itself, and skip this section if you want to only use RushCheck! If you have an interest to our development version (not yet shipped), then try to access our darcs repository. darcs is a version control system and can be easily installed from the darcs page. After installing darcs, you can get the current development of RushCheck by following:
% darcs get --partial http://rushcheck.rubyforge.org/repos/rushcheckAfter darcs getting, you can get the newest files whenever you want to type
% darcs pull -a
If you have installed RushCheck using RubyGems, then you should add the following two lines to your test codes.
require 'rubygems' require_gem 'rushcheck'
Otherwise, if you have installed RushCheck by setup.rb, then add the simple one line.
require 'rushcheck'The following maybe useful if you don’t matter whether you use rubygems or not.
begin require 'rubygems' require_gem 'rushcheck' rescue LoadError require 'rushcheck' end
require 'your_library'
OK, then we can start to write test codes. RushCheck requires to write assertion based testcases. An assertion of function (or method) consists of triple where inputs, guard conditions and a testing property block. Here is a templete of assertion:
RushCheck::Assertion.new(Class1, Class2, ...) do |var1, var2, ...| # testing property block # this block should return boolean value (true, false or nil) # in Ruby end
For example, assume that we want to test the method ‘sort’ in Array. The following assertion is a simple testcase:
ast_zero =
RushCheck::Assertion.new() do
[].sort == []
end
whether if we sort empty array the result is also empty. This assertion does not have any inputs and guards but only have the property block.
Let’s see another example:
ast_one =
RushCheck::Assertion.new(Integer) do |x|
[x].sort == [x]
end
This assertion defines that we claim for any integer ‘x’, an array [x] is not changed after sorting. We can test the property 100 times:
irb> ast_one.verbose_check 1: [-1] 2: [-2] ... (snip) ... 99: [6] 100: [1] OK, passed 100 tests. true irb> ast_one.check OK, passed 100 tests. true irb>
RushCheck supports random testing such as above. We will see later how to change the number of test, how to change the distribution, etc. Here we learned two testing methods, verbose_check and check. ‘verbose_check’ displays every instances of test, on the other hand ‘check’ does not display inputs but only the result.
Next example shows how RushCheck displays the counter example. If an assertion is failed, then there is a counter example of the assertion.
ast_two =
RushCheck::Assertion.new(Integer, Integer) do |x, y|
[x, y].sort == [x, y]
end
The above test is failed sometimes and we can find the result after checking.
irb> ast_two.check Falsifiable, after 3 tests: [2, -1] false irb>
Here, the counter example [2, -1] of the assertion is appeared. In fact, [2, -1].sort equals [-1, 2] and does not equal [2, -1].
Sometimes, we need several pre-conditions for tests. For example, if we have a pre-condition ‘x <= y’ in the previous assertion, then the assertion should be succeeded. We can write pre-conditions as guards in the property block:
ast_two_sorted =
RushCheck::Assertion.new(Integer, Integer) do |x, y|
RushCheck::guard { x <= y }
[x, y].sort == [x, y]
end
irb> ast_two_sorted.check OK, passed 100 tests. true irb>
Note that it is always assumed that the number of arguments of Assertion.new must be equal to the number of variables of the block. (Until ver 0.3, experimentally the number of arguments can be differed, however from ver 0.4, they must be equal.) The arguments of Assertion.new corresponds to the variables of block one to one. We can have any number of guards in the property block. If the guard property does not hold in random testing, then the test is abort and try to take another random instances. We can imagine the following test sequences in ast_two_sorted.
In the previous section, we saw two methods ‘check’ and ‘verbose_check’. Sometimes, we want to watch the statistics of random testing. However ‘check’ shows less information, and ‘verbose_check’ is too noisy. RushCheck has several options to watch the statistics.
You may not want to check the trivial case in random test. As we have seen, we can ignore the trivial case using the guard condition. However, on the other hand, we can watch how many trivial cases are appeared in random test.
ast_sort_triv =
RushCheck::Assertion.new(Integer, Integer) do |x, y|
RushCheck::guard {x <= y}
([x, y].sort == [x, y]).trivial{ x == y }
end
irb> ast_sort_triv.check OK, passed 100 tests(14%, trivial). true
Here, we have 14% (i.e. 14 times) trivial (x == y) cases in the test.
In addition, we can give another names to watching statistics.
ast_sort_classify =
RushCheck::Assertion.new(Integer, Integer) do |x, y|
RushCheck::guard {x <= y}
test = ([x, y].sort == [x, y])
test.classify('same'){ x == y }.
classify('bit diff'){ (x - y).abs == 1 }
end
irb> ast_sort_classify.check OK, passed 100 tests. 18%, same. 11%, bit diff. true irb>
The library ‘test/unit’ of Ruby is useful for unit testing. Here is a trick to use ‘test/unit’.
def forall(*cs, &f)
assert(RushCheck::Claim.new(*cs, &f).check)
end
The class Claim is a subclass of Assertion. The meaning is almost similar to Assertion, however Claim does not check the result value of the given block ‘f’. Because assertions in ‘test/unit’ such as ‘assert_equal’ does not return any result, but return nil, we don’t need to check the result values of a sequence of assertions. Nevertheless we can check the testcase because the assertions in ‘test/unit’ raises an exception if the testcase is failed.
Example:
def test_not_empty_after_push
array = Array.new
forall(Integer) do |item|
array.push item
assert(! array.empty?)
end
end
def forall(*cs, &f)
RushCheck::Claim.new(*cs, &f).check.should_equal true
end
Then, for example, we can write a specification of Array#push as follows:
context "An empty array" do
specify "should not be empty after 'push'" do
forall(Integer) do |item|
array = Array.new
array.push item
array.should_not_be_empty
end
end
end
See also ‘examples/rspec’ in the distribution of RushCheck for details.
In previous sections, we have seen how to check assertions for any integers. In similar way, we can define the assertions for any float numbers or any string.
RushCheck::Assertion.new(Float, String, ...) do |ratio, name,...| # testcase end
RushCheck has default random generators for the following basic classes:
If you want to change the distribution of randomness, then you have to write a code for generator. There are some examples for writing generators. In the next section, I will introduce SpecialString whose distribution differs to the default implementation of String.
Even Array, Hash and Proc are also primitive classes, however the randomness of them should be changed in testing codes. Therefore, RushCheck provides an abstract generators for them. Programmer need to write a subclass of the abstract generators. Later I will show you how to write a subclass of RandomArray, etc.
Sometimes, we want to check special characters, for example the backslash ’\’ or unprinted characters ‘ESC’, ‘NUL’ and so on. SpecialString is a subclass of String which is defined in ‘rushcheck/string’. This library is already required in ‘rushcheck/rushcheck’ and don’t need to require again.
The output of random generator of SpecialString has different distribution of its of String. At String, the distribution of characters are normalized. On the other hand, the distribution in SpecialString is weighted and the generator provides special characters more often than alphabets. In detail, the distribution is as follows:
| the distribution of SpecialString | |
|---|---|
| Alphabet | 15% |
| Control characters | 50% (such as NUL) |
| Number | 10% |
| Special characters | 25% (such as ’\’) |
Using SpecialString in assertions, it is more likely to find the counter example. The following example is the famous bug which is called ‘malformed format bug’.
malformed_format_string =
RushCheck::Assertion.new(String) { |s| sprintf(s); true}
malformed_format_string2 =
RushCheck::Assertion.new(SpecialString) { |s| sprintf(s); true}
irb> malformed_format_string.check
Falsifiable, after 86 tests:
Unexpected exception: #<ArgumentError: malformed format string - %&>
... snip error traces ...
["\n&'e!]hr(%&\031Vi\003 }ss"]
false
irb> malformed_format_string2.check
Falsifiable, after 15 tests:
Unexpected exception: #<ArgumentError: malformed format string>
["%\037-R"]
false
In these results, we can see RushCheck find the counter example after 86 tests with String, on the other hand, find it quickly after 15 tests with SpecialString.
It is easy to change the distribution in SpecialString. You can define your subclass of SpecialString as follows:
class YourSpecialString < SpecialString
@@frequency = { 'alphabet' => 1,
'control' => 0,
'number' => 0,
'special' => 9 }
end
The meaning of randomness for Array must be changed in testing codes. Sometimes you needs a random array of Integer, and another a random array of String. So what is random array?
RushCheck provides an abstract class RandomArray for abstract random generator. Programmer have to write a subclass of RandomArray as follows:
# for random array of integers
class MyRandomArray < RandomArray; end
MyRandomArray.set_pattern(Integer) {|ary, i| Integer}
The class method set_pattern takes a variable and a block. Because array is inductive structure, it can be defined by the basecase and the inductive step.
For example, let’s consider a random array in the following pattern
[Integer, String, Integer, String, ...]
where it has a random Integer at the odd index and a random String at
the even index. Then we can write a random array with this pattern:
MyRandomArray.set_pattern(Integer) do |ary, i| if i % 2 == 0 then Integer else String end endMore complecated example? OK, let’s consider a stream:
MyRandomArray.set_pattern(Integer) do |ary, i| if ary[i].kind_of?(Integer) && ary[i] >= 0 then String else Integer end end
In this way, we can define any random array with any pattern.
Hash is also primitive and not so clear what is a random hash, like array. RushCheck provides an abstract random generator RandomHash, and programmer can write a subclass of RandomHash.
class MyRandomHash < RandomHash; end
pat = { 'key1' => Integer, 'key2' => String }
MyRandomHash.set_pattern(pat)
In this example, we can get a random Hash with two keys (‘key1’ and ‘key2’). Here the keys are String, but we can give any object as usual in Hash. Is it clear to define random hash? I think so. (If not it is clear, then the interface may be changed in future)
It is not difficult to create a random Proc object. As we saw in the previous sections, we have to write a subclass of RandomProc.
class MyRandomProc < RandomProc; end MyRandomProc.set_pattern([Integer], [Integer])
Here, we define a random procedure which takes an integer and returns an integer also. In general, Ruby’s function and method can be regarded as a relation. (not a function in mathematics!) Therefore I think random procedures can be generated by a pair of inputs and outputs.
Let’s consider a simple example. In general, any functions f, g, and h should satisfy the associativity property:
for all x, f(g(h(x)) === (f . g)(h (x))
where f . g is a composition of functions in mathematical way
class Proc
# application
def **(other)
Proc.new do |*args|
res = other.call(*args)
call(*res)
end
end
end
class MyRandomProc < RandomProc; end
def associativity_integer
MyRandomProc.set_pattern([Integer], [Integer])
RushCheck::Assertion.new(MyRandomProc, MyRandomProc,
MyRandomProc, Integer) do
|f, g, h, x|
(f ** (g ** h)).call(x) == ((f ** g) ** h).call(x)
end.check
end
P.S. The arbitrary method is used to create a random object for test instance. Then you may wonder what is the coarbitrary method? The coarbitrary method is used to generate a random procedure (lambda), which is one of central idea in QuickCheck.
RushCheck::Assertion.new(YourClass) { |obj, ...| ...}
you have to write a code which generates a random object in your
class. Therefore, at first we have to consider
what is a random object in YourClass?
Sometimes the answer is not unique; there may be several ways for generating random objects. Like SpecialString in RushCheck, you can also write an abstract random generator.
OK, after thinking about the question, we have to write a code. To define random generators, we have to add a class method arbitrary in YourClass.
class YourClass
extend RushCheck::Arbitrary
def self.arbitrary
# override the class method arbitrary
...
end
end
If you need to define a random proc which returns a object in YourClass, you have to include Coarbitrary, also.
class YourClass
extend RushCheck::Arbitrary
include RushCheck::Coarbitrary
def self.arbitrary
...
end
def coarbitrary(g)
...
end
end
However, because it is little complecated to implement both arbitrary and coarbitrary, let’s focus how to implement arbitrary first.
Let’s consider the first example Candy. The Candy class requires its name and its price at initialize. The name should be a String, and the price Integer. The Candy class may have several instance methods, but they are omitted because not important to define the random object.
class Candy
def initialize(name, price)
raise unless price >= 0
@name, @price = name, price
end
def foo
...
end
def bar
...
end
end
To write random generator, we have to look up ‘initialize’. Here, assume that @name belongs String and @price belongs Integer. It is natural that we assumes @price should be positive.
One simple random generator for Candy:
class Candy
extend Arbitrary
def self.arbitrary
RushCheck::Gen.create(String, Integer) do |name, price|
RushCheck::guard { price >= 0 }
new(name, price)
end
end
end
Gen.create takes an array of Gen object (here, [Integer.arbitrary, String.arbitrary]) and a block. The block takes variables which is corresponded to the array, as Assertion.new. If guard is failed, then RushCheck retry to create another random instance.
Note that we can use a trick instead of the guard property.
price = - price if price < 0 # g.guard { price >= 0 }
In this case, this is more efficient to generate random instance
than with the guard. However, sometimes we don’t have this kind
trick and we can use some guards.
Remark: from version 0.4, Gen.create is changed to require classes in its argument from Gen objects.
class Candy
extend RushCheck::Arbitrary
def self.arbitrary
String.arbitrary.bind do |name|
Integer.arbitrary.bind do |price|
price = - price if price < 0 # trick as I described above
RushCheck::Gen.unit(new(name, price))
end
end
end
end
Puzzled? OK, they can be readed as follows:
In general, the class method arbitrary should return a Gen object. Check also gen.rb in RushCheck. There are several combinators to create random generators.
There are several way to implement random generators. The next example is to use Gen.new without bind and unit.
class Candy
extend RushCheck::Arbitrary
def self.arbitrary
RushCheck::Gen.new do |n, r|
r2 = r
name, price = [String, Integer].map do |c|
r1, r2 = r2.split
c.arbitrary.value(n. r1)
end
price = - price if price < 0 # trick
new(name, price)
end
end
end
This pattern is useful if your class has many valiables for initialize. Because binding patterns needs much depth of method call chains, it may be failed. This technique is used to avoid the stack overflow. This is one difference between Haskell and Ruby.
The implementation can be understanded as follows:Note that we need new random generator for each random object. Here we create new random generator by spliting. If you use same random generator for generating different objects, then the distribution of objects are same (not generated randomly).
Because sometimes the initialize of YourClass is complecated, then the random generator self.arbitrary turns to be complicated also.
In next sections, I will introduce several generators in gen.rb. They may be useful to create your own random genrator. See also rdoc of Gen.
RushCheck::guard { price >= 100000 } # very expensive candy!
then it consumes much time to generate random instance because the
guard fails so many times. When RushCheck creates random instances,
it starts smallest as possible, then the size becomes larger in
repeating tests. Therefore, if the guard instance seems failed so
often, then we need another seeds of generators. Here is another
generators for expensive candy instance.
lo = 100000
g = RushCheck::Gen.sized { |n| RushCheck::Gen.choose(lo, n + lo)}
xs = [String.arbitrary, g]
See gen.rb and the following sections in details for how to get
another generator.
To help defining random objects in your class, there are several functions in Gen class.
Gen.choose(lo, hi) returns a Gen object which generates a random value in the bound.
example.Gen.choose(0, 10) # a generator of Integer in (0..10)
Gen.frequency requires an array of pair of Integer and Gen objects, and returns a Gen object. Gen.frequency is used to define the distribution of randomness.
example.Gen.frequency([[3, Integer.arbitrary], [7, String.arbitrary]]) # return a random integer or a random string (by choosing # randomly) Integer:30% String: 70%
See also SpecialString.
Gen.lift_array takes an array and a block which has a variable. The block should return a Gen object. lift_array returns a Gen object which generates an array of the result of given block for applying each member of given array.
example.
class Candy
extend RushCheck::Arbitrary
def self.arbitrary
RushCheck::Gen.lift_array([Integer, String]) do |c|
c.arbitrary
end.bind do |args|
new(*args)
end
end
end
Gen.oneof requires an array of Gen objects returns a Gen object.
example.Gen.oneof([Integer.arbitrary, String.arbitrary]) # return a random integer or a random string (by choosing # randomly)
Gen.promote is used to generate a random Proc object. Next section I will describe how to define the random Proc object. See also proc.rb in the example directory of RushCheck.
Gen.rand is a random number generator.
Gen.sized is used to change the size of random object. See also some implementation in RushCheck (grep the source!)
return Gen object.
Get a vector of Gen.
example.Gen.vector(Integer, 3) # => Gen [Integer, Integer, Integer]
so on, grep ‘coarbitrary’
FIXME: how to write coarbitrary
There is no mailing list for RushCheck (now at 2006-08-08), but don’t hesitate to contact to the author!
Happy hacking and testing!