Trastel, the DSL I created to help with the automated testing of our services at Orbitz, has been accepted as the QA team’s official testing language. Yippee!
As I have mentioned before, I’ve been working on an (internal) DSL at work to aid with testing. I just finished reading a post by Michael Feathers entitled How to Fail with External DSLs. It’s a good post that brings to light some very real issues regarding the development of external domain specific languages. I laughed to myself when Michael identified one of the pitfalls as being the inability to attract new hires to work on a language that they would not be able use outside of your company, as I turned down a job right out of college for that exact reason.
We were recently given a free day at work to hack on a project that was outside the realm of our normal responsibilities, yet could still be beneficial to the company. We were encouraged to be creative, explore ideas that interested us, and see if we could come up with something to demo at the end of the day.
Service level testing, or functional testing, has been a hot topic at work recently. It’s no secret that we have a very large SOA at Orbitz, powered by Jini. Services, calling services, calling services…you get the picture. Historically, we have not been the best at automating the testing of these services. That is beginning to change. About two years ago, the team I work on developed a test tool that allows us to interact with our services at a very high level, keeping us out of the nitty-gritty details when invoking a service. It’s a command line based tool with a very simple, intuitive syntax. All of the details are accessible if we need them, but more often than not they just get in the way. In fact, we enjoyed working at this level of abstraction so much, that I wrote a functional test framework (named jwoodunit by my co-workers) that drove service level tests against our services, which were written in this same “language”. It allowed us to pump out service level tests is very short order that were easy to read, and easy to maintain. It has only just occurred to me that what we had created was really a Domain Specific Language (DSL).
We have a fairly large quality assurance team that is made up mostly non-developers. Most of the testing that is done is manual, or driven by an automation tool similar to Selenium. The problem is that the manual testing is slow and not reliable, and tools like Selenium tend to be brittle, since they are based on the layout of the HTML page. It also prevents you from testing any service that is not directly accessible through the web application. So, for my project, I wanted to see if I could take my team’s DSL, clean it up even more (it is still very “programmy”), and give it to our quality assurance team so that they could test our services in a more reliable fashion.
What I ended up with was a very high level, English like DSL that I call Trastel (Travel Service Testing Language). Trastel is implemented in Ruby. In fact, it’s safe to say that Trastel is Ruby. I didn’t implement a new language. I simply took advantage of Ruby’s fantastic meta programming capabilities to extend the language with functionality that is needed by the tests. A example test is worth 1000 words:
search.flights.on("orbitz").where( :origin => "ORD", :destination => "LAX", :departure_date => "2008/12/10", :return_date => "2008/12/15" ) verify_at_least_one_result foreach_result do verify_equal "ORD", origin end
This does exactly what you’d expect. It searches flights on Orbitz, flying from Chicago’s O’Hare airport to Los Angeles International Airport on 2008/12/10, returning on 2008/12/15. We then verify at least one result came back, and that the origin of each flight is O’Hare. That’s it.
Implementing this test in Ruby was a breeze. Since everything is an object in Ruby, dynamically adding methods to the Object class gives us the ability to create pseudo-keywords, like “search”, and “foreach_result”. Trastel also sets an attribute named @response on the test, so the code that implements the verify methods can just check that, instead of having to specify that your checking the response of the service call. foreach_result will iterate over the @response if it is an array, yielding to the specified code block for each item in that array…giving us an easy way to check each element. The last bit of magic surrounds the “origin” method. “origin” isn’t a method on object. It’s a method on the the type contained in the @response. Thanks to Ruby’s method_missing method, I can forward that method call onto the object in the @response for it to tell me what origin means. Nice and easy.
Another great thing about Trastel is that it sucks in Active Support. Active Support brings with it a wealth of extensions to the core ruby classes, letting us do something like this:
search.flights.on("orbitz").where( :origin => "ORD", :destination => "LAX", :departure_date => 4.weeks.from_now, :return_date => 6.weeks.from_now )
We still do all of the heavy lifting (creation of the service request, execution of the remote service, etc) in Java, using JRuby to get the Ruby code to play nice with the Java code. But, the DSL serves as an excellent means to collect the data needed to drive the test, call the Java code to invoke the service with that data, and verify the data in the response matches what was expected. It successfully keeps the person writing the test from having to know anything about actually invoking the service. Writing tests at this level of abstraction also shields us from the majority of service API changes that may come our way.
With all of the benefits that I can already see with this still very immature DSL, I’m kicking myself for not considering DSLs more seriously in the past. They have been around quite some time, and have had many well know developers singing their praises. I’m looking to see what other problems I’m currently facing that could be solved a little easier by letting me code a little closer to the domain. Had I know that it would be this simple, maybe I would have tried it a long time ago.