One of the things that has always fascinated me about dynamic languages is how you go about testing them.
See, the trick is that when I write a closed implementation of some sort — whether it be a closed class or module — I’ve got some concept of the boundaries that my object’s state could be in. I’ve got a system which is locked in, probably with some state-machine-like semantics. So my unit tests wrap the flows that I understand, and verify that trying to violate those flows fails in some known way, and then I have reasonable confidence that things are good.
But dynamic languages that offer open implementations (like Perl, Groovy, and Ruby) really shoot that approach in the foot. Anything can be touched, and anything can be changed, and there’s nothing you (the original implementor) can do about it. This means that your unit test suite is going to be more incomplete, in the sense that your unit tests will exercise a much smaller portion of the field of possible ways in which your code might be used.
Worse, a common pattern in dynamic language development relies on external intervention in order to have it make sense. It’s extremely postmodern, actually: the code is gibberish without a context, making calls to methods and variables that simply don’t exist unless you have some kind of intervention. At this point, efforts to unit test are doomed to failure, because without the external intervention, there is nothing meaningful to test. With the external intervention, you’re really talking about an integration test.
Any interesting example of this is the particular case of controllers and views in Grails and Rails. Controllers are notoriously difficult to unit test, because they’re riddled with dynamically provided methods. Efforts to solve this via mocking are really treading on dangerous ground.
My solution right now is to not unit test them at all.
I used to be writing integration tests, which looked a lot like unit tests but fired up the framework before attempting to execute them. But that’s slow and dreary work, and I found it to generally be more effort than it was worth: inevitably, I ended up writing far more code to get to the particular unit test case than it took to do th implementation in the first place, and I often ended up mocking up so much that I was basically testing my mocking worked plus an if statement.
Enter functional tests. Before I start coding up a new controller, I start with writing functional tests (currently with Canoo WebTest through Grails WebTest Functional Testing) which encounter that controller and exercise the functionality that I want to see. This gets me all the guaranties that the integration testing provided, plus it provides a regression test suite that strongly resembles the user stories coming down the pipeline, and it exercises my views as well as my controllers.
Related posts:
Pingback: Upped The Recent Post/Popular Post Widget Count | Enfranchised Mind