MXUnit 1.0.3 Now Available
Designing for testability: today's real-world example
- It's a lot easier to test arrays for the data they contain than it is to test the database after the fact
- Since there's only one chunk of code that performs a database Update, I can easily "turn the update off", thereby not updating the database at all.
<cffunction name="moveSection" returntype="array" hint="resorts stuff" access="public">
<cfargument name="blah">
<cfset var sections = ArrayNew(1)>
<cfset var ii = 1>
<cfset var jj = 1>
<cfset var kk = 1>
<cfset var keyList = "">
<cfset var updateTemplateSections = "">
<!--- assload of logic to build up an array --->
.....
<!--- process array and extract sectionIDs in order --->
<cfloop from="1" to="#arrayLen(sections)#" index="ii">
<cfset keyList = listAppend(keyList,sections[ii].sectionID)>
<cfloop from="1" to="#arrayLen(sections[ii].children)#" index="jj">
<cfset keyList = listAppend(keyList,sections[ii].children[jj].sectionID) />
</cfloop>
</cfloop>
<!--- finally, do the update --->
<cfloop from="1" to="#listLen(keyList)#" index="kk">
<cfquery name="updateTemplateSections" datasource="#getDsn()#">
UPDATE templateSections
SET sortOrder = #kk#
WHERE templateSectionId = #listGetAt(keyList,kk)#
</cfquery>
</cfloop>
<cfreturn sections>
</cffunction>
As I said, in my test, I want to do two things: Test the array for its data, and ensure the database update doesn't occur. This method, as originally written, almost gets me there. To the original programmer (my friend Mike), nice job! The obvious problem here, with respect to testing, is that the update will still happen. So what to do? Since the original code has a single block for the DB update, it's easy for me to pull out that chunk of code into a separate method, and then inside my test, override that method with one that simply doesn't perform any updates. I call this "Extract and Override". Here's my new code. It now contains 2 methods. I've simply extracted out that last chunk of DB updates into a separate method, and then I call that in the original function:
<cffunction name="moveSection" returntype="array" hint="resorts stuff" access="public">
<cfargument name="blah">
<cfset var sections = ArrayNew(1)>
<cfset var ii = 1>
<cfset var jj = 1>
<cfset var keyList = "">
<!--- assload of logic to build up an array --->
.....
<!--- process array and extract sectionIDs in order --->
.....
<!--- call my new method, which I've extracted --->
<cfset performMoveSectionUpdate(keyList)>
<cfreturn sections>
</cffunction>
<cffunction name="performMoveSectionUpdate" access="private">
<cfargument name="keyList">
<cfset var kk = 1>
<cfset var updateTemplateSections = "">
<!--- finally, do the update --->
<cfloop from="1" to="#listLen(keyList)#" index="kk">
<cfquery name="updateTemplateSections" datasource="#getDsn()#">
UPDATE templateSections
SET sortOrder = #kk#
WHERE templateSectionId = #listGetAt(keyList,kk)#
</cfquery>
</cfloop>
</cffunction>
Finally, the unit test. Since I've extracted out the actual code that performs the update, it's trivial to override that with MXUnit in the test. All I do is create a new, private function inside the test itself that does nothing. Then, I use injectMethod to replace the existing function with my new for-testing-only function:
<cffunction name="moveSectionDownResortsCorrectly">
<cfset injectMethod(gw, this, "performMoveSectionUpdate", "performMoveSectionUpdate")>
<cfset sections = gw.moveSection(arg1,arg2,arg3)>
<!--- perform assertions as needed --->
</cffunction>
<cffunction name="performMoveSectionUpdate" access="private">
<cfreturn "">
</cffunction>
Bottom line:
- in my original object, extract out the behavior that I don't want to occur, during my test, into a separate method
- in the test, override that method in my original object with a new, innocuous method
RIA as Motivator for Server-Side Test-Driven Development
Carl Jung coined the term "synchronicity" to describe events that at first appear coincidental but which are potentially meaningfully connected.... a "meaningful coincidence". I've been thinking about this for some time now: that the advent of Rich Internet Applications (RIAs) could potentially be a tremendous boon for encouraging boring old server-side unit testing. Now, you might be thinking: "the two are completely unrelated". You can't get much more disparate. RIAs are sexy, they're hip, they're sleek and fun and whizbang. Unit testing, on the other hand, has got to be the least sexy coding you can do. It's the toothless, saggy-butt, scraggy-haired 4th cousin of cool technologies, at least to many people. Cranking out animated heatmap hoogies in flex is toot sweet. Writing "assertEquals(4,query.recordcount)" ain't.
Back when I started writing web apps in CF, debugging was pretty simple because you had easy access to the information you needed in order to debug. You load a page. If you have problems, you maybe throw in a cfdump/cfabort. Or you submit a form and want to see that the DB update query code looks OK, so you comment out the redirect in your index.cfm file, and then look at the debug output at the bottom of the page to check that your database updates are as expected. Snip snap done. But when you're building richer interfaces, this work is often either done asynchronously using javascript (ajax), in which case you can't rely on your old tricks; or, even more difficult, it's done in a movie client (flex/flash, silverlight) and you're even further removed from the information you need in order to do your debugging.
The further removed you are from development-time access to debug information, the more time you spend debugging development-time problems.
Let that sink in. There is no cfdump/cfabort simplicity when your front end is a flash movie making remote calls to your backend CFCs. With richer interfaces, the time you spend debugging your backend code increases considerably. Not so sexy anymore, is it? But here's the thing: What if, rather than try to debug your code at runtime in the interface, you instead remove the interface from the equation. You stay in CF for your debugging. You get your CFDumps back. In short, you get closer to your data again... you get closer to the information you need to do your debugging. You're no longer so far removed.
This is where our ugly cousin, unit testing, comes in for the win. By testing your backend CF code with more CF code, you stay on the server and you catch your problems earlier. You codify the expectations of the code. You think more about the edge cases. You root out bugs earlier in the process and thereby make writing your RIA joyful again. You can focus your front-end efforts on the pizazz because you're not worrying so much that your queries are working as expected -- you've already worked out the server-side kinks in your tests. The common arguments against unit testing -- that it takes too much time, that you write too much code, etc -- start to fall away as it becomes apparent that the time savings you accrue start to overtake the lost time spent trying to debug server-side code from an RIA.
This is what I've been thinking about: that RIAs make it harder to debug server-side code. Therefore, they encourage more unit testing of server-side code because the value proposition of unit testing is clearer when put in contrast to the drudgery of runtime debugging the backend of an RIA.
Which leads to my moment of synchronicity today. I had planned on writing this blog post on how RIA development damn near requires backend TDD. Then, I came to work this morning, and in my feedreader was this: Indy Nagpal gave a presentation on server-side TDD at a recent RIA conference. Not at CFUnited or CFObjective or the local CFUG. At an RIA conference. I thought that was pretty cool, and it suggests to me that other (way smarter) people are thinking the same thing: that if you're writing rich internet apps, then server-side unit testing is getting harder to ignore.
--marc
MXUnit Eclipse Plugin Test History Feature

- No duplicate entries. "com.my.SomeTest" will only ever appear once in the list, and will only reflect the last run. If you run that test 20 times, you won't get 20 entries in the list
- When running a directory of tests, you see the full path to the directory in the list
- It's easy to distinguish between single TestCase runs and Directory runs. Simply, directory runs start with a slash.
- A more reasonable start value for remembered test runs. JUnit defaults to 10 items in the history. Dunno why. MXUnit defaults to 30. Considering that duplicates never appear, this seems like a reasonable default. In addition, changing the default is simple and obvious.