My Journey into Test Driven Development

If you wonder about Unit Testing and Test Driven Development, but are afraid of commitment, do yourself a favour, read Terrance Ryan's thoughts on "How to convince yourself to Unit Test". A couple of months ago, I made a commitment to start unit testing. I'll admit that it took a little while to wrap my head around the terminology and process. A good practice point for me was to run some of my first CFCs though Seth Petry-Johnson's var scope checker. When I discovered a plethora of un-scoped vars, I used the opportunity to write tests against the methods I was going to change. After only a few tests, I started to get this warm feeling of security. By the time my updates and tests were complete, I had a nice little suite that I could return to any time a change was required.

Prior to unit testing, I would test changes like this by navigating to a page within my application (perhaps a few clicks), trigger the method (maybe a form post, or clicking a link to a list page) and observe the output. More often than not, I would forget to re-initialize the application to refresh the component in Coldspring. I would often use <cfdump /> and <cfabort /> and then remove or comment out that code if I got the expected result.

For my most recent project, I set up some initial tests, but continued to debug through the front end. The project uses ColdSpring, ModelGlue and Transfer. After a few painful, wasteful debugging sessions (add and MG event and listener, add a controller method to set values, add Service method to return value to controller), I set up some tests and was able to diagnose and fix issues faster than my previous process. Suddenly, that feeling of security returned.

And now that I'm a fair way into the project, I have, by degrees, come to embrace the "test first, test often" credo. It was a paradigm shift, but not polar extreme. If I thought that I always wrote error-free code, I wouldn't bother unit testing. Reality is, I'm going to have to debug something at some point and I can save headaches and perform variations of tests quicker by embracing TDD than sticking with front end <cfdump /> and <cfabort /> testing. While I don't consider myself a TDD guru, I am feeling more confident and dedicated to the process.

Here are a few terms common to Unit Testing that you can expect learn when you get started.

Test Suite - A collection of test cases. Often you'll find an example suite named "AllTests",

Test Case - A collection of tests. A test is nothing more than a method that contains an assertion of a testable behaviour from your object.

Test Fixture - within a test case, you'll have several test for the same class. A unit testing framework will provide a way to "prep" the environment for your tests with methods to setup and teardown the test environment. Using setup, you create a test fixture.

Test Harness - A test harness is used to automate testing. I consider it an advanced topic, something to grow into when you're ready to try continuous integration.

Assertion - Assertions are the heart of unit testing. They provide the means for defining an expected result from your test. With assertions, you typically test for a return type, or value. When I started testing, I would run a type test first then a value test second. I have since changed to testing "behaviour" by setting the test to either pass or fail with an expect type and value.

Mock Object - A goal of unit testing is to test the methods of your objects in isolation. To achieve this end for objects that invoke methods on objects it depends on, it is essential that you "mock" the dependency. If you use CF8, you can achieve a high degree of isolation and control over you mocks, by using Brian Kotek's ColdMock.

Even though there is a lot to absorb when it comes to learning Unit Testing and Test Driven Development, I've found the process very interesting and rewarding. If you're sitting on the fence when it comes to unit testing, take an evening to dive in. The water's not too deep and you may come out feeling refreshed.

Comments
marc esher's Gravatar Funny you mention the "front-end cfdump / cfabort" stuff. For me, when I started unit testing, not having cfdump really (really!) hindered me. Another thing I missed was all the goodies you get in cfcatch. I mentioned this to a colleague of mine who was unit testing at the time, and he said he just writes little one-off scripts to do the cfdumping and whatnot and then throws them away. I was like "so you write a test, then you write another test?" Screw that! I used JUnit for a long time, and I never had to jump out of the test to see what I wanted to see (data, exception info, etc). Sometimes a system.out.println() is all you need to see what's wrong and keep you moving. It was a real sore spot for me with CF unit testing.

Long story short (beware... plug alert!): mxunit came about as a way to make unit testing simpler, and one of the things we put in there fairly early was a facility for seeing your data. cfdump, cfoutput, cfcatch. whatever. no more one-off throwaway scripts, etc. It saves me a ton of time!

One other thing unrelated to frameworks at all... this notion of a test harness. I ran across something a little bit ago, a really nice image from a book called "Test Driven". Its focus is java testing but the concepts are the same. Anyway, the author's image of a test harness is one of a fat inner circle and a thin outer circle. the outer circle is test test harness. the inner circle is your application. thus the test harness keeps your app together. And when you break your harness (your tests aren't updated, you leave big chunks untested, etc), the application can "leak" out and get all nasty. I liked the metaphor because it was a good way for me to digest the concept. You mentioned that it's a more advanced concept related to automation. You know what I do? I set up a scheduled task in CFAdmin that runs a run.cfm file. run.cfm is about 10 lines of code. it runs a directory of tests then emails me. nothing fancy! if you're into ant, you can really do some cool things with a combination of a few simple ant tasks. I think at least to start, most shops don't need a full blown fancy continuous integration server with oodles of metrics and reports and whatnot. an email at noon that says "100 tests passed, but these 3 failed" is often just enough to keep you honest.

Thanks for the great post! I love these posts (like yours and terrence's and sam larbi's recent one) that make unit testing seem so much more accessible than it has seemed in the past... at least to me. I feel like it's always been one of those "you should do it... like you should exercise, and eat brussel sprouts, and take robitussin" kind of things. Very daunting and enterprisey and all that but not for regular folks. But not anymore.
# Posted By marc esher | 2/3/08 4:00 PM
Seth Petry-Johnson's Gravatar It's awesome to hear about someone using my VarScopeChecker CFC for unit testing. I designed it with TDD in mind, so I'm glad it's working out that way.

@Marc: I know what you mean about unit testing being "daunting and enterprisey and all that", especially at the beginning. The good thing is that (In my experience, anyway), automated testing is one of those things where even an incomplete solution is a huge improvement over no solution at all!
# Posted By Seth Petry-Johnson | 2/4/08 8:15 AM
Paul Marcotte's Gravatar @Mark, Thanks for the comments. I'm currently using CFCUnit, but I have checked out mxunit. I like the option to create custom assertions and the Ext view for the test runner is a nice touch. Thanks, also for the clever "lo-fi" suggestion to automate tests using a scheduled task. I'll give that a run for sure.

@Seth, Thanks for building an awesome tool! It helped me get into the write a little, test a little frame of mind so I could fix un-scoped vars and test the changes immediately to ensure I wasn't breaking anything.
# Posted By Paul Marcotte | 2/4/08 10:34 PM
BlogCFC was created by Raymond Camden. This blog is running version 5.9. Contact Blog Owner