I’m working on a couple of different Ruby on Rails applications which have me thinking about testing. One has an insufficient number of tests, and the other has no tests at all.
Why write unit tests?
The first thing I’ve come to realize is the degree to which I depend on unit tests in getting my job done. Implementing features is a whole lot faster when you get serious about testing. Let me give you a simple example. In a content management system, I added a field to hold a short description to a table that already had a description field. Because the new field is not populated for any existing records, I needed a method that would show the short description if it is populated, show the full long description if it’s small enough, or cleverly truncate the long description if it’s too long to be a short description.
This is the type of task where writing tests can cut down on development time. You set up a few mock objects to represent the possible scenarios, and then write some tests to make sure your code behaves correctly in all of them. Back in the day I would have written the code, used the Web interface to create records representing each possible state, and then tested each of those states by inspecting the results in the Web interface. It would have wound up taking forever (since I made several little mistakes while writing the method), and in the end I wouldn’t have had a test suite to test for bugs that creep in when the code is modified later.
Why Rails developers hate database constraints
In the one application that has no tests, I’ve seen why many Rails developers are not in favor of using foreign keys. They make using fixtures (the means that Rails provides for getting mock data into your test database) pretty darned difficult. Unit testing is great, but there’s nothing more frustrating than wasting your time debugging and fixing your testing environment. Maintaining application code is exhausting, burning time maintaining your tests is just exasperating.
The thing about fixtures is that they’re stupid (and I mean that as a compliment). You put some data in a file and Rails sticks it in the database. It doesn’t apply any of the validation in your application to it, and that’s great, because the only thing you really care about is whether your tests exercise the right parts of the application and get the results you need. If you’re using some mock user data to test a shopping cart, the fact that you didn’t populate all of the fields in the user table might not matter. Database constraints demand that you include data in your test environment that you may not really need, and generally slow down the test-writing process.
Foreign keys are even worse. Fixtures are inserted before each test and deleted after the test has run, but in some cases they can leave stale data in the database, causing subsequent tests to blow up, telling you nothing about the quality of your code and slowing down your development. Seeing a test work when run individually and then die when the full test suite is run is frustrating.
Configuring your database to defend itself from the insertion of bad data can be a good thing (and if you work with a DBA, they’ll probably demand it), but I’m not sure if the value overrides the added difficulty when it comes to writing tests. I’d rather have a well-tested application with a good suite of automated tests than a database with constraints. I’ve just seen too many working applications that are just fine without them.
Why unit tests are doubly important with scripting languages
Compiled languages (like Java) have an advantage over scripting languages like Ruby that unit tests can equalize. When you compile a Java program, the compiler will let you know about most of the typos you made, all of the times you referenced a variable by the wrong name, and all of the other easy to find errors. Any decent Java IDE will let you know about those kinds of errors before you even save the file you’re working on.
When you’re working with a scripting language, you don’t have the luxury of relying on the compiler to do this kind of work for you. That’s where unit tests come in. If nothing else, a suite of unit tests that opens and compiles every file in your project will save you testing time, even if those tests don’t actually exercise any of your logic.
I came to this realization when I started working on the Rails application that has no tests and realized that it contained a bunch of files that actually had syntax errors in them. (I found this out by trying to run all of the unit tests, which, of course where in the same state they were in when the Ruby on Rails code generator created them.) These kinds of mistakes are easy to make if you don’t have some mechanism to prevent them. I see it all the time, especially in the rarely used corners of PHP applications.