DDT

Data driven testing for Ruby with RSpec. DDT kills bugs.

DDT is a plugin for RSpec that makes data-driven testing easy. It allows you to define a YAML file for a specific method that includes test input and expected outputs. See below for an example.

License

DDT is released under the MIT license. See the LICENSE file.

Requirements

DDT requires RSpec because, well, it's an RSpec plugin. In the future, I may make DDT more general and let the RSpec part be optional.

DDT should work on any Ruby. It's only been tested on Ruby 1.9.3 so far, though.

Installation

If your system is set up to allow it, you can just do

gem install ddt

Or, if you prefer a more hands-on approach or want to hack at the source:

git clone git://github.com/simplifi/ddt.git
cd ddt
rake install

If you are working on a system where you need to sudo gem install you can do

rake gem 
sudo gem install ddt

As always, you can rake -T to find out what other rake tasks we have provided.

Basic Usage

Right now, DDT::TruthTesting that is the main workhorse of DDT. DDT::TruthTesting adds some methods to RSpec to enable a truth testing. DDT::TruthTest automates the process of creating an object, calling a method on it with some specified input, and checking the state of the object afterwords against what is expected, i.e., the "truth". DDT::TruthTest defines a class called Truth for the class under test; the Truth class encapsulates the input and expected output.

This example and the others below are taken from spec/truth_test/truth_test_spec.rb

First, include ddt in your test suite. Probably this just means adding require 'ddt' to your spec_helper.rb

Suppose we have a classed called Cat that has a state called disposition and a method called pet:

class Cat
  attr_accessor :disposition

  def initialize
    @disposition = "mrow."
  end

  # cats are temperamental.  my cat does not
  # like to be petted on the belly, but she
  # won't let you know until afterwords.
  def pet how
    predisposition = @disposition
    if how =~ /belly/
      @disposition = "*hiss*"
    end
    predisposition
  end
end

We can write a truth testing spec file for Cat#pet like this:

describe Cat do
  truth_test :pet
end

When we run rspec, DDT will look for a file called truths/cat/pet_truths.yaml containing YAML test cases. Here's an example:

---
input: "on the head"
output: "mrow."
disposition: "mrow."
---
input: "on the belly"
output: "mrow."
disposition: "*hiss*"

For each of these test cases, DDT::TruthTest will create a new Cat instance and call the pet method with "input" as the argument. It then does an RSpec example that looks something like

cat.pet("on the head").should == "mrow."

as well as

cat.disposition.should == "mrow."

Mapping test data

DDT::TruthTest lets us map test data using Object::define_truth, which takes as arguments an instance of the object under test and a hash corresponding to the YAML test case. As an example, consider the Dog class here:

class Dog
  attr_accessor :name, :weight, :age

  def parse! str
    d = str.split(",")
    @name = d[0]
    @weight = d[1].to_f
    @age = d[2].to_i
    self
  end
end

# example:
rover = Dog.new.parse! "rover", 31.5, 5
rover.name   # => "rover"
rover.weight # => 31.5
rover.age    # => 5

In our test data, the weight needs to be converted to a float and the age needs to be converted to an int, so we'll manually add some conversions to our truth class:

Dog::define_truth do |dog, data|
  # make age an integer
  dog.age = data["age"].to_i

  # make weight a float
  dog.weight = data["weight"].to_f
end

We can then define truths for Dog#parse! in truths/dog/parse\!_truths.yaml (note the \!):

---
input: "Fido, 30.0, 3"
name: "Fido"
weight: 30.0
age: 3

And test in RSpec:

describe Dog do
  truth_test :parse!
end

Note, in the Cat example above, Cat::define_truth is called automatically by truth_test.

Specifying the test instance

The Truth class also provides a method called Truth#tester that supplies the instance to test. By default, Object#new is called with no arguments to supply the instance. This method can be overridden to specify other arguments:

class Wolf
  attr_accessor :call
  def initialize call
    @call = call
  end
end

# we have to first create the truth class
Wolf::define_truth

# then we can monkey patch the tester method
class Wolf::Truth
  def tester
    self.class.tester || Wolf.new("AOOOO")
  end
end

It's also possible to use RSpec's stubbing to do this:

Wolf::Truth.should_receive(:tester).and_return(Wolf.new "draw blood")

Contributing

The usual github process applies here:

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Added some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request

You can also contribute to the author's ego by letting him know that you find String Eater useful ;)