Complex tests - use a DSL instead of fixtures

I’ve been finding I use fixtures less and less lately. I am working with
rather complex testing of code that does data analysis. Each test may
involve 5-10 tables and some of the data is different in nearly every
test. I’ve found it much more readable to build a mini-DSL for the
setting up the database rather than using a huge amount of fixtures. It
has the added benefit of being able to see the state of your database in
the same spot as the test. Trying to hop back and forth from the unit
tests to 5-10+ yml files to check all that a test is doing can really be
frustrating. The place I’m still finding them useful is for core data
that is used in many different tests across many different test suites.

Any thoughts?

Jack

I agree that flipping between multiple fixture files can be quite
tedious. Can you provide examples of how you use the DSL instead of
fixtures?

-Jonathan.

It’d be great if you could share some examples of what you’ve done; I’m
running into a smaller version of the same problem.

Thanks!
Daniel

What is the meaning of ‘mini-dsl’ ??

Thanks

On Friday, July 28, 2006, at 6:06 PM, Daniel H. wrote:

test. I’ve found it much more readable to build a mini-DSL for the


Rails mailing list
[email protected]
http://lists.rubyonrails.org/mailman/listinfo/rails


Rails mailing list
removed_email_addres[email protected]
http://lists.rubyonrails.org/mailman/listinfo/rails

This may not be quite the same thing, but I’ve been getting away from
fixtures a lot lately. I add some code to my mocks that let me generate
objects that match certain criteria. For example, I usually define a
method to create a valid object and an invalid one (by AR standards).

I then create one of these objects and feed it to my tests, depending on
what I need it to do. Since I also test the factory functions to make
sure they actually create valid or invalid objects, I have a lot more
confidence that the tests are working properly. Otherwise I would need
go and maintain my fixtures frequently.

There may be some performance issues using this approach, but I haven’t
looked into it very much. Perhaps I need to be generating the fixtures
on the fly and re-using them throughout each test bundle.

_Kevin
www.sciwerks.com

nuno wrote:

What is the meaning of ‘mini-dsl’ ??

Thanks

DSL = domain specific language. Mini, because in this case, it usually
only has several methods.

Wikipedia description of DSL’s -
http://en.wikipedia.org/wiki/Domain_Specific_Language

Jack

aniel Higginbotham wrote:

involve 5-10 tables and some of the data is different in nearly every
Jack

I can’t release the actual code, and it wouldn’t really make sense
outside of my employer’s unique situation, but here is a rough example
that should illustrate the idea.

The scenario is a delivery company. In particular, testing code that
estimates the profit of jobs. In this simplified scenario we will have
only six tables: drivers, cargo types, trucks types, customers, jobs,
and job cargoes. Drivers contain the pay per mile. Truck types both
contain the maintenance cost per mile of operation as well as the fuel
efficiency. Cargoes contains the pay per mile for different cargoes.
Customers can contain a discount percentage if they are under a bulk
contract. A job has one driver, has one truck type, has one customer,
and has many job cargoes. A job also contains the distance traveled and
the current price of fuel.

Tests would something look like this:

def test_job_with_customer_discount
driver_pay_per_mile 0.43
truck_costs :maintenance_per_mile => 0.25, :miles_per_gallon => 7.6
customer_discount 0.8
cargo :amount => 200, :pay => 7.25
cargo :amount => 5, :pay => 30.0
fuel_price 3.20
distance 850

assert_estimated_profit 400
end

With fixtures you would have the following:

def test_job_with_customer_discount
assert_equal 400, jobs(:with_customer_discount).estimate_profit
end

But you would need to hop through 6 YML files keeping track of all FK’s
in your head to figure out what it was actually testing against.

To make the DSL style test work you would have something like this:

def setup
@job = Job.new
end

def driver_pay_per_mile( p )
@job.driver = Driver.new( :pay_per_mile => p, … other required stuff
for valid driver )
end

def truck_costs( hash )
@job.truck_type = TruckType.new( hash.merge( …other required truck
type stuff ) )
end

def customer_discount( discount )
@job.customer = Customer.new( :discount => discount, … other
customer stuff )
end

def cargo( hash )
new_cargo_type = CargoType.new :pay_per_mile => hash[:pay_per_mile,
… other cargo type stuff
@job.job_cargoes<< JobCargo.new( :cargo_type => new_cargo_type,
:amount => hash[:amount]
end

def fuel_price( price )
@job.fuel_price = price
end

def distance( miles )
@job.distance = miles
end

def assert_profit( p )
assert_equal p, @job.estimate_profit
end

Jack