The following code is an example of what I decided I wanted the DSL to look like (for a state based test, expectations also has support for behavior based testing).
Expectations do
expect :expected do
:expected
end
end
When making a DSL executable I generally begin by building up objects with the values expressed in the DSL. For example, the above code creates a test suite containing 1 expectation where the expected value is :expected and the actual value can be determined by evaluating the block. While designing expectations I tested it using expectations, but that's probably more complex than is necessary for this example, so I'll simply print values instead.
The first step is ensuring that an Expectations::Suite is created when Expectations is called with a block. The following code demonstrates that an Expectations::Suite is created successfully. The code executes since the block given to Expectations is never executed.
; end
include Singleton
end
Expectations::Suite.instance
end
Expectations do
expect :expected do
:expected
end
end
Expectations::Suite.instance # => #<Expectations::Suite:0x1d4fc>
note: Expectations::Suite is a singleton so that it can be appended to multiple times and later used for execution without being stored within another object.
The next step is to verify the number of expectations the suite stores for later execution. The following code verifies that one object is stored in the expectations array.
; end
include Singleton
attr_accessor :expectations
self.expectations = []
end
expectations << :expectation
end
end
Expectations::Suite.instance.instance_eval(&block)
end
Expectations do
expect :expected do
:expected
end
end
Expectations::Suite.instance # => #<Expectations::Suite:0x1c82c @expectations=[:expectation]>
Expectations::Suite.instance.expectations.size # => 1
The final step is to create an object for each expectation and verify the values of both the expected and the actual.
; end
include Singleton
attr_accessor :expectations
self.expectations = []
end
expectations << Expectations::Expectation.new(expected, actual)
end
end
attr_accessor :expected, :actual
self.expected, self.actual = expected, actual.call
end
end
Expectations::Suite.instance.instance_eval(&block)
end
Expectations do
expect :expected do
:expected
end
end
Expectations::Suite.instance # => #<Expectations::Suite:0x1b184 @expectations=[#<Expectations::Expectation:0x1b0e4 @expected=:expected, @actual=:expected>]>
Expectations::Suite.instance.expectations.size # => 1
Expectations::Suite.instance.expectations.first.expected # => :expected
Expectations::Suite.instance.expectations.first.actual # => :expected
note: expectations actually defers determining the value of actual until the suite is executed, but that's outside the scope of this example.
The bottom two lines of the last example show that the Expectations::Expectation instance is being populated successfully.
That's it, at this point the DSL can successfully be parsed. Of course, making the expectations run to verify that the values match still needs to be done, but that's no different than working with any plain old ruby code. The DSL can be evaluated and the framework can work with the objects that have been created.
As the example demonstrates, designing an internal DSL in Ruby is very easy largely due to open classes, class methods, and the ability to eval. The above code for evaluating the DSL is less than 30 lines of code.
Jay, you seem to have missed a small detail in the second code example, in the implementation of the Kernel Expectation method. In the code it takes a &block but doesn't send that block along to anything. By design?
ReplyDeleteOla, thanks for the comment.
ReplyDeleteI purposefully did nothing with the block so I could iteratively show building up the different objects. Perhaps it's confusing, but I like that you can paste the code in and run it to see that it works.
Anyway, the next example shows what I do with it. =)
Cheers, Jay
It's not clear to me why you went to all this trouble. This "language" looks like on obfuscated way of making a few method calls. Wouldn't it be simpler to build and less brittle for end-users to just provide some basic OO methods to accomplish the same thing?
ReplyDeleteto mr anonymous:
ReplyDeletei think you missed the point. this post was all about showing users how jay might go about writing DSLs which in turn might help other people who want to write DSLs.
In addition, Expectations is his take on doing testing.
Anonymous:
ReplyDeleteI'm not really sure how to respond.
> This "language" looks like on obfuscated way of making a few method calls.
Fair enough, what would you like to see instead?
> ...provide some basic OO methods to accomplish the same thing?
Again, I'm not sure what you are looking for. Can you provide an example?
Cheers, Jay