Requirements
 
Prerequisite knowledge
Working knowledge of ColdFusion components. Minimal understanding of MXUnit, unit testing, and CFEclipse will also be helpful.
 
User level: Intermediate
 
 
 
Required products
Adobe ColdFusion Enterprise Edition (2016 release) (Download trial)
 
 
Sample files
 

 
Additional Requirements

 
MXUnit Framework and Eclipse Plugin
"Testing is not the same thing as having tests."
—Kent Beck, Test Driven Development: By Example
 
Test Driven Development (TDD) is the disciplined practice of writing unit tests prior to writing the actual production code. This practice can lead to clean, well-designed software that is agile and easily adapted to rapidly changing requirements. Additionally, having tests for your code gives you a platform for immediate feedback on the state of your software and communicates your intentions to team members. The process is simple; yet, like anything done well, it takes time to learn and practice. You write tests for your code first, quickly write just enough code to make the tests pass, then refactor the code until it satisfies the system's requirements, ensuring your test is passing as you are refactoring. Changes to code are done in small increments, referred to as baby steps. While developing, you will frequently run your unit tests. This gives you that immediate feedback.
 
This two-part series will demonstrate how to apply TDD and unit testing techniques to ColdFusion application development. It will show you how you can leverage the MXUnit toolset to build unit tests and automated test suites that give you, the developer, an almost instantaneous assessment of your code while facilitating collaboration in a team environment.
 

 
No fear!

Excessive pressure to deliver can be the demise of software quality, and fear also prevents you from trying something new, possibly faltering along the way, but ultimately succeeding. Take a leap of faith and try unit testing and TDD; we promise you will learn something new and valuable!
 
Note: TDD implies that the developer writes unit tests as they code; however, you can still write unit tests without practicing TDD. Each developer must decide when to write tests. It has been our experience that one of the most beneficial things you can do for your development process is to write unit tests for your code, as you develop your code. We wouldn't have spent hours upon hours building a freely available test framework and Eclipse plug-in for ColdFusion if we didn't strongly believe this ourselves. Not only are we the tool builders, we are some of its most demanding users!
 

 
Terminology

Unit Test: Code that when run, exercises and tests your production code at an atomic level.
 
Production Code: The code that gets executed when the application is run. This is typically what your customer is paying for.
 
Test Case: A specific test that executes some production code and checks its state or behavior.
 
Test Suite: A collection of test cases that can be run together.
 
Assertions: Functions that test the state of an object. For example, assertEquals(expected value, actual value), assertTrue(boolean expression).
 
Red-Green-Refactor: The TDD mantra. Red indicates a failing test; Green is a passing test (see Figure 1); and Refactoring is the process of altering the code's implementation.
 
Baby Steps: Small incremental changes to your code and unit tests.
 
Note the pretty Green bar. Green is good; it indicates a passing test in MXUnit.
Figure 1. Note the pretty Green bar. Green is good; it indicates a passing test in MXUnit.
 

 
TDD in action: Show me the money!

The following example is intended to demonstrate some TDD basics, and in Part 2 we will show you some advanced unit testing techniques that will answer some of the inevitable questions that arise shortly after getting started.
 
 
The Twitter client
Twitter is a popular micro-blogging and social networking tool that allows its users to stay in touch with people they follow, by posting and reading 140-character tweets, or entries.
 
 
Functional requirements
  • This component will read Twitter and make the data available in ColdFusion.
  • The data will consist of the 20 most recent tweets of the people the user is following.
  • The production code will use HTTP to retrieve the data from Twitter. (The Twitter API is REST-based and is open to anyone who has a Twitter account.)
To run the example and associated tests, you will need to set up an account at Twitter and follow a few people. (Alternatively, you should be able to learn the basics of unit testing and TDD with ColdFusion without running the samples, by simply reading Part 1 and Part 2 of this series and exploring the accompanying source code.)
 
For more information on the Twitter REST API see the documentation.
 
 
Where do you start?
At this point, resist the urge to simply bang out some code that will read the Twitter feed and send JSON to an Ajax client. Remember, this is an exercise in TDD. Breathe in. Breathe out. Focus.
 
One technique to articulate your understanding of the requirements is to write a test list. In natural language, write the tests you feel will cover your requirements on paper or sticky-notes or a to-do list. This is an agile approach to specification, too, and when ultimately implemented as unit tests, provides some excellent documentation in addition to testing your code. After you write the test list, one by one write the unit tests (which will initially fail), followed by the production code that enable the tests to pass.
 
For this example, start with the following tests:
 
  • Twitter should be alive (a ping test)
  • The Twitter account should be valid (a verify credentials test)
  • The Twitter Friends timeline should return 20 items (feed test)
You will add more tests later; remember, baby steps.
 
The Twitter API states that there is a test URL that returns ok if successful. A first run at the should be alive test may look like this:
 
<cffunction name="twitterShouldBeAlive"> <cfset var twitter = createObject("component","TwitterClient")> <cfset var expected = 'ok'> <cfset var actual = twitter.ping() > <cfset assertEquals(expected,actual)> </cffunction>
This test expects the ping method on the TwitterClient component to return the string 'ok' and nothing else.
 
To run this and the other tests, make sure you have MXUnit installed and then point your browser to http://localhost/twitter/TwitterClientTest.cfc?method=runtestremote. For these examples, test results are illustrated using the MXUnit Eclipse Plugin.
 
When you run this test in MXUnit, it will fail with the message "Could not find the ColdFusion Component or Interface TwitterClient."
 
The Red portion of Red-Green-Refactor.
Figure 2. The Red portion of Red-Green-Refactor.
 
Believe it or not, this is good! This is the Red part of the Red-Green-Refactor rhythm mentioned earlier (see Figure 2). This is telling you what code you need to write. So, go for it; write the ping method in TwitterClient.cfc:
 
<cffunction name="ping" hint="Runs the twitter api test command."> <cfhttp url="http://www.twitter.com/help/test.json" method="get"> <cfset response = deserializeJSON(cfhttp.FileContent)> <cfreturn response> </cffunction>
You know from the Twitter API that the HTTP response body should contain a simple string, 'ok'. You need to deserialize the JSON object to escape the double-quoted string in the HTTP response body. Now, run the test (see Figure 3).
 
Feel the power of Green!
Figure 3. Feel the power of Green!
 

 

This code works fine, but you would probably want to throw an exception if there is a problem with Twitter, instead of leaving it all up to the client. So, refactor the code and leave the test as is—the test is still valid!
 
<cffunction name="ping" hint="Runs the twitter api test command."> <cfset var response = 'not ok' /> <cfhttp url="http://www.twitter.com/help/test.json" method="get"> <cfset response = deserializeJSON(cfhttp.FileContent)> <cfif response is not 'ok'> <cfthrow type="TwitterPingFailure" message="Twitter might be down" detail="Twitter says #cfhttp.FileContent#"> </cfif> <cfreturn response> </cffunction>
When you run the should be alive test again, you will still see that pretty Green bar. Can you see how having a test allows you to refactor your production code quickly?
 
That's the first item on the test list. Take a look at the second item: The Twitter account should be valid (a verify credentials test).
 
Again, write the test first. It should look something like the following:
 
<cffunction name="theTwitterAccountShouldBeValid"> <cfset var twitter = createObject("component","TwitterClient").> <cfset var actual = twitter.verifyCredentials(uname,pwd) /> <cfset assertTrue(actual)> </cffunction>
In the TwitterClientTest.cfc source code there is a setCredentials() utility function for reading credentials in from a text file. You could code your credentials as a variable, too. They're not published here for obvious reasons.
 
When you run this you'll again be in the Red (see Figure 4). Feel the rhythm. Be one with The Test.
 
Red again; the new test has failed.
Figure 4. Red again; the new test has failed.
 
You know what to do now, right? Write the verifyCredentials() production code in TwitterClient.cfc. Don't worry too much about how you're implementing it. It's going to change. But that's OK because you now have a test that you can use to make sure it's doing what you want. Baby steps.
 
<cffunction name="verifyCredentials" hint="Tests that credentials are valid."> <cfargument name="uname" type="string"> <cfargument name="pwd" type="string"> <cfreturn true /> </cffunction>
When you run your test, you will quickly be in the Green. But wait! The code doesn't really do anything, it just returns true. Red, Green, Refactor. You've implemented the interface, but now, refactor it so that it does what you want it to do, all the while trying to keep your tests Green.
 
<cffunction name="verifyCredentials" hint="Tests that credentials are valid."> <cfargument name="uname" type="string"> <cfargument name="pwd" type="string"> <cfset var response = {} > <cfhttp url="http://www.twitter.com/account/verify_credentials.json" method="get" username="#arguments.uname#" password="#arguments.pwd#"> <cfset response = deserializeJSON(cfhttp.FileContent)> <cfif not structKeyExists(response,'id')> <cfthrow type="TwitterAuthenticationFailure" message="Could not log into Twitter." detail="Tried user:#arguments.uname# pwd:#arguments.pwd#" /> </cfif> <cfreturn true /> </cffunction>
Run the test again, and checkthe results (see Figure 5).
 
The test results are still Green after refactoring.
Figure 5. The test results are still Green after refactoring.
 
Now for a bit of MXUnit TDD Kung-Fu. You know that good credentials work; how can you test for bad credentials? Is there something you can do with the TwitterAuthenticationFailure exception maybe? Add this next item to your test list: Invalid Credentials Should Throw TwitterAuthenticationFailure. Copy and paste into a new test (remove the spaces, of course):
 
<cffunction name="invalidCredentialsShouldThrowTwitterAuthenticationFailure" mxunit:expectedException="TwitterAuthenticationFailure"> <cfset var twitter = createObject("component","TwitterClient") /> <cfset twitter.verifyCredentials('Kwai Chang Caine','Grasshopper') /> </cffunction>
This test verifies that an exception is thrown by the production code when you pass in invalid credentials. This is also nicely articulated by the test's name. Note the special custom attribute in the cffunction tag – mxunit:expectedException="…". This statement provides an instruction to MXUnit to expect that type of exception. If that exception is not thrown, the test will fail.
 
Load and run your tests again (see Figure 6).
 
A test for the expected exception has been added.
Figure 6. A test for the expected exception has been added.
 
Next on the list is The Twitter Friends timeline should return 20 items (feed test). You know the steps. Write the test and watch it fail (Red), write the production code and run the test again (Green), and change the production code as needed (Refactor).
 
First, the test:
 
<cffunction name="twitterFriendsTimelineShouldReturn20Items"> <cfset var twitter = createObject("component","TwitterClient") /> <cfset var results = twitter.friendsTimeline(variables.uname,variables.pw)> <cfset debug(results)> <cfset assertEquals(20, arrayLen(results), "Something other than 20 items were returned.") /> </cffunction>
The test is telling you now to add a friendsTimeline() method to TwitterClient and that the method needs to accept username and password parameters.
 
<cffunction name="friendsTimeline" hint="returns the user's friends timeline"> <cfargument name="uname" type="string"> <cfargument name="pwd" type="string"> <cfset var response = {} > <cfhttp url="http://www.twitter.com/statuses/friends_timeline.json" method="get" username="#arguments.username#" password="#arguments.password#"> <cfset response = deserializeJSON(cfhttp.FileContent)> <cfreturn response> </cffunction>
When you run this test you will feel the joy of Green (see Figure 7).
 
The results of the twitterFriendsShouldReturn20Items test.
Figure 7. The results of the twitterFriendsShouldReturn20Items test.
 
 
Refactoring TwitterClient
There are some obvious problems with TwitterClient. There's some code duplication that can be changed. There's also the problem with repeatedly passing around user credentials. It would probably be better to set the user's credentials when the TwitterClient instance is created, that is, in init(). So, add some tests to make sure init() does what it's supposed to do: init() should set credentials.
 
<cffunction name="initShouldSetCredentials"> <cfset var twitter = createObject("component","TwitterClient")> <cfset twitter.init('Master Po', 'gimme my walking stick')> <cfset assertEquals('Master Po', twitter.getUserName(), 'Username not set')> <cfset assertEquals('gimme my walking stick', twitter.getPassword(), 'Password not set') > </cffunction>
When you run this, again you'll be in the Red. So, listento the test; it's telling you exactly what you need to write:
 
<cffunction name="init" hint="Initializes TwitterClient"> <cfargument name="uname" type="string"> <cfargument name="pwd" type="string"> <cfset this.userName = arguments.uname > <cfset this.password = arguments.pwd > <cfreturn this > </cffunction>
In addition, you will need to write getters and setters—accessor methods—to return the username and password.
 
Now, for each test, one by one:
 
  1. Change the test so that the credentials are set in init(). For example:
<cfset var twitter = createObject("component","TwitterClient").init(uname,pw)>
  1. Remove any credentials from the method calls in TwitterClientTest. For example:
<cfset twitter.verifyCredentials() />
  1. Run the test, which will be Red (fail).
  2. Refactor TwitterClient to remove the credentials as parameters and use getUserName() and getPassword() instead.
<cffunction name="friendsTimeline" hint="returns the authenticated user's friends timeline"> <cfset var response = {} > <cfhttp url="http://www.twitter.com/statuses/friends_timeline.json" method="get" username="#getUserName()#" password="#getPassword()#"> <cfset response = deserializeJSON(cfhttp.FileContent)> <cfreturn response> </cffunction>
Run the tests again. You'll be Green (see Figure 8).
 
Refactored and still Green.
Figure 8. Refactored and still Green.
 
There's still plenty of room for refactoring and making the code even cleaner. Here are some ideas that are also listed in the source code under the directory named "better":
 
  • Leverage MXUnit's setUp() method to initialize TwitterClient and remove the createObject() statement from each test. This will reduce the amount of code to read.
  • Reduce code duplication in TwitterClient by creating instance data for the Twitter URL and Twitter output format (JSON or XML).
You now have a number of tests that you can run anytime you need to make changes to TwitterClient. Furthermore, your team (and you, too, many days from now) can look at the tests and get a clear picture of what your intentions were at the time this was written.
 

 
Where to go from here

One thing you may have noticed is that the tests may be slow. This is because of the dependency upon your network and upon Twitter itself. This is a common challenge when writing unit tests and there are also common solutions that will make your tests run faster while ensuring that your application does what it's supposed to do. These solutions will be covered in Part 2 of this series.
 
Note that the size of the methods is small and their intent is focused on a single task. This not only makes the code easier to test, but makes it cleaner, easier to read, and easier to maintain.
 
Having a suite of tests allows you to run many tests very quickly and frequently. The tests serve as great documentation, illustrating specific intent with your code. Some developers go even as far as to omit comments in their code in favor of tests.
 
This article has demonstrated the practice of TDD with respect to a simple and small application. If applications were all this simple, developers would be testing all the time. But the reality is that good software is hard to write and hard to test. Also, adopting TDD at an organizational level is yet another challenge unto itself.
 
You may very well have tons of questions. This is a good thing. Proceed to Part 2 for some more real-world approaches to some of the tough questions. If you haven't already done so, download and install MXUnit and join the MXUnit Google Group.
 
Test and be happy!
 

 
References