This post originated from an RSS feed registered with Ruby Buzz
by Christian Neukirchen.
Original Post: DSLs for TDD and BDD
Feed Title: chris blogs: Ruby stuff
Feed URL: http://chneukirchen.org/blog/category/ruby.atom
Feed Description: a weblog by christian neukirchen - Ruby stuff
Of course, I had to give that a try too. I don’t have any code for my
DSL yet, it doesn’t even have a real name either. For comparision,
let’s have a look at a sample of Test::Unit, taken from the Ruby Test First
Challenge:
require 'test/unit'
require 'SimpleSpread'
class TestSpread < Test::Unit::TestCase
def test_that_cells_are_empty_by_default
sheet = Sheet.new()
assert_equal("", sheet.get("A1"))
assert_equal("", sheet.get("ZX347"))
end
def test_that_text_cells_are_stored
sheet = Sheet.new()
a1 = "A string"
sheet.put("A1", a1)
assert_equal(a1, sheet.get("A1"))
sheet.put("A1", "foo")
assert_equal("foo", sheet.get("A1"))
sheet.put("A1", "")
assert_equal("", sheet.get("A1"))
end
end
If you don’t understand this, you probably are wrong here; it’s
standard Test::Unit usage.
Have a look at the structure of that test case. Tests are grouped
into test cases that inherit from Test::Unit::TestCase. Then, you
define methods, the actual tests (which have a name) inside. All
these methods start with test_. There are two special methods
setup and teardown that are run before and after the test. Every
test has actions and assertions. If assertions fail, the test
ends and Test::Unit will report it to have failed.
testcase: TestSpread
test: test_that_cells_are_empty_by_default
setup
assertion
assertion
test: test_that_text_cells_are_stored
setup
action
assertion
action
assertion
action
assertion
Fast forward to my testing language, for now called Desire.
Here is the code:
In Desire, things work differently. First, note that you simply can
read out the source and it will make sense. You don’t need to create
a class to hold tests, selection purely works by tagging tests.
Tags can be any Ruby object, in above case we use a class and a symbol
to tag the following tests. To the reader of the testcase, this
means: We test related to the class Sheet and the interface (because
the test is independent from the implementation).
After this, we declare a basic situation—a common ground—we wish
to use for our tests. Here, it consists of initializing an instance
variable (more about that later) to a new instance of Sheet.
Then, an expect block follows. All tests in Desire are declared
inside expect blocks. Here, we declare five(!) tests, which are
grouped using two descriptions. To define a test, you use two
methods, doing and results_in. doing calls the block and saves
its return value. With results_in, you either can check against a
predefined value (by passing it as argument) or by providing a check
on your own (by passing a block that should return true).
Every pair of doing/results_in defines a new test, which is run
despite of the outcome of the other tests. Therefore, we have this
structure:
tagged: Sheet, :interface
setup
description: cells_are_empty_by_default
test
action
assertion
test
action
assertion
description: text_cells_are_stored
test
action
assertion
test
action
assertion
test
action
assertion
This is, at least in my humble opinion, far clearer and more
structured than the former one.
If I get around to actually implement Desire, I could well imagine
basing it on Test::Unit, though. It’s a solid base and easy enough to
be used with metaprogramming.
Problems left open for now are the use of instance variables to
communicate the common ground with the tests and minor syntactic
issues.
Other ideas for defining assumptions:
doing { File.open "feeble" }.
raises Errno::ENOENT
doing { a }.
or { b }.
or { c }.
results_in common_result # or: results_in_same