Last week I wrote about the benefits of Domain Specific Languages (DSLs). Since then I’ve been thinking and realised that DSLs are even more important when writing tests. It just so happened that I was writing tests in Emacs Lisp for a package I wrote called cmake-ide, and given that Lisp has macros I was trying to leverage them for expressiveness.
Like most other programmers, I’ve been known from time to time to want to raze a codebase to the ground and rewrite it from scratch. The reason I don’t, of course, was aptly put by Joel Spolsky years ago. How could I ensure that nobody’s code would break? How can I know the functionality is the same?
The answer to that is usually “tests”, but if you rewrite from scratch, your old unit tests probably won’t even compile. I asked myself why not, why is it that the tests I wrote weren’t reusable. It dawned on me that the tests are coupled to the production code, which is never a good idea. Brittle tests are often worse than no tests at all (no, really). So how to make them malleable?
What one does is to take a page from Cucumber and write all tests using a DSL, avoiding at all costs specifying how anything is getting done and focussing on what. In Lisp-y syntax, avoid:
(write-to-file "foo.txt" "foobarbaz") (open-file "foo.txt") (run-program "theapp" "foo.txt" "out.txt") (setq result parse-output "out.txt") ;; assertion here on result
Instead:
(with-run-on-file "theapp" "foo.txt" "foobarbaz" "out.txt" result ;; assertion here on result
Less code, easier to read, probably more reusable. There are certainly better examples; I suggest consulting Cucumber best practices on how to write tests.
Not every language will offer the same DSL liberties and so your mileage may vary. Fortunately for me, the two languages I’d been writing tests in were Emacs Lisp and D, and in both of those I can go wild.