Christian Cantrell
Macromedia
Unit Testing ColdFusion Components with CFUnit
Note: The CFUnit components are part of DevNet Resource Kit (DRK) Volume 3, which is available until midsummer 2003 exclusively to DevNet Professional and DevNet Essentials subscribers. After midsummer 2003, DRK Volume 4 will release, and DRK Volume 3 and all previous volumes will be available for $99 each.
Find out more about DevNet Subscriptions
>
Unit testing means writing code to test code. It is most important in circumstances where developers write "low-level" code or software components that are intended to be used by other components or otherwise extended.
There are two primary ways that unit testing differs from end-user or QA testing:
- The code it tests is generally not accessible from a front end or user interface. It's often lower-level than presentation code.
- The code it tests is easily quantifiable and measurable. This means it can be tested programmatically; a subjective tester doesn't have to test it.
Writing unit testing code for software components is a good idea because you can catch bugs in low-level code before they reach the user-interface level, where they are often quite difficult to track down.
Note: This article was originally published as part of DevNet Resource Kit Volume 3. To proceed with this tutorial, you'll need the CFUnit components, which are part of DevNet Resource Kit Volume 3.
Requirements
-
ColdFusion MX
- Components for this tutorial are part of DevNet Resource Kit Volume 3.
Why Do You Need CFUnit?
Let's assume you built a ColdFusion component called calc.cfc, which does various mathematical calculations for you. You then use calc.cfc in an application, which retrieves housing prices out of a database, calculates monthly payment scenarios at various interest rates and loan durations, and then displays all the data in an HTML table.
If you do not unit test calc.cfc and other low-level code, and someone on the QA team discovers that some of the numbers in the HTML table look incorrect, you might have difficulty tracking down the bug. It could be that the data in the database is wrong, the SQL that retrieves the data from the database is wrong, there is an error in the presentation or number-formatting logic, there is an error in the way you use the calc component's API, or there is simply an error in calc.cfc itself. By writing unit testing code to cover as many situations as possible, you just have to run the code and quickly inspect the easy-to-read results to narrow down the possibilities.
The importance of unit testing code increases the more you are able to reuse and layer your code. For instance, let's say you build a component called bank.cfc that models common bank transactions and makes heavy use of calc.cfc. As before, if you find errors in bank.cfc, you won't know whether the errors reside actually in bank.cfc or calc.cfc unless you can unit test both components.
Unit testing is also invaluable when you decide to refactor software components. Unit tests can tell you instantly whether your refactoring actually broke the component or other components that depend on it.
Unit testing is extremely important in environments where multiple developers work on a single project. For instance, if I am writing an application that calculates loan amounts based on real estate information in a database and one morning I update my source tree with all the latest code, it's going to be extremely inconvenient and a huge waste of my time if I update a bunch of little bugs checked in by other developers. If the other developers unit tested their code before checking it in, however, they could catch most of their bugs before they would affect other people's work.
In the past, there was not much need for unit testing in ColdFusion because ColdFusion was primarily a presentation language that lacked a sophisticated ability to put together software components. With the introduction of ColdFusion components, however, a unit testing framework not only became extremely useful, but also was practical to implement and use.
Developers who don't like unit testing code usually argue that it's a waste of time. In other words, for every component you write, you end up having to write a test component along with it, which adds time to the development process. Developers who have been writing unit testing code for a long time, however, counter that the time you save in tracking down and fixing low-level bugs is well worth the relatively small amount of time it takes to write unit tests.
Unit testing frameworks like CFUnit make the process of writing unit testing code as efficient as possible. Some developers go so far as to write unit testing code before implementing the components that the tests are actually designed to test. Unit testing code is a good way to exercise your API and make sure it's usable and practical.
What Are the CFUnit Testing Components?
The CFUnit package is used for unit testing ColdFusion components. Other than what I call the "meta" test code (the code that tests the test code), the package contains only two components: test_case.cfc and test_suite.cfc.
Test Cases. The test_case component is the primary component used to create unit tests. In fact, every CFUnit unit test you write ultimately extends test_case. When a component extends test_case, it becomes a unit test because it inherits all the functions that test cases need. Test functions include things like "run" (which tells the test to start running) and several assert functions (functions that actually test and compare data).
To create a ColdFusion component unit test, follow these steps:
- Write a component that extends com.macromedia.test_case. Let's assume you are writing a unit test for calc.cfc. Your unit test might be called something like calc_test.cfc and should live in the same package as your calc component.
- Inside calc_test.cfc, define the variable testName as the name of your test; an appropriate name might be Calculator Unit Test.
- Optionally create your own setUp and tearDown functions. If you override test_case's default setUp and tearDown functions, setUp will be called just before each of your test methods is called and tearDown will be called just after. setUp and tearDown allow you to perform any initialization or cleanup that your test functions might need.
- Write as many test functions as you want. Any function in your unit test that begins with the word "test" will automatically be called when the test is run. (More on test functions in the next section.) Because test functions are called in random order, they should not depend on one another.
-
Run your test by calling the run function through a URL in your browser. For instance, if calc_test.cfc sits in the wwwroot/com/domain/util directory, run the test by typing the following URL in your browser:
http://localhost/com/domain/util/calc_test?method=run
Your test case will automatically generate an HTML table of test results.
Test Function Standards. As I stated above, test functions do the actual testing. All your test functions should observe the following standards:
- Begin each test function with the word "test."
- Name each test function in lowercase letters and make use of underscores, not capital letters, to separate words.
- There is no limit to the number of test functions you can have in a unit test. If fact, the more you have, the more confident you can be in your code.
- Test functions should test something very specific; their names should reflect what they test. For instance, "test_adding_two_numbers."
- Test functions usually use one or more assert statements inherited from test_case to determine that something is either true or not true. (More on assert functions in the next section.) For instance, "test_adding_two_numbers" would probably call the "assertEquals" function to make sure that 2 + 4 = 6.
Assert Functions. There are 10 assert functions (functions that begin with the word "assert") contained in test_case.cfc. Each one allows you to test something slightly different, depending on the nature of the component. Although different assert functions take in different arguments, they all use a "message" argument that displays a message when the function fails.
Test Suites
A test suite is a named collection of test cases that allows you to run the entire collection at once. The ideal way to write test cases is to create one for every component and store it in the same package as the component it's testing. You can then create a single package-level test suite that contains and runs all the tests in the package. That way, you can run all your unit tests easily and regularly to make sure no bugs have been introduced below the user-interface layer.
Installing the CFUnit Components
Because the CFUnit components are contained in the com.macromedia.CFUnit package, the easiest way to install them is to create the directory structure com/macromedia/cfunit in your web root and copy all the CFUnit files into the cfunit directory.
Components can also be placed in a directory under a ColdFusion mapping, or in a custom tag root.
Using the CFUnit Testing Components
I have already explained the process of creating a CFUnit test for a component called calc.cfc. Below is what code might actually look like:
<cfcomponent extends="com.macromedia.cfunit.test_case"
output="yes">
<cfset testName = "Calc Unit Test" />
<cffunction name="test_adding_two_numbers"
access="package">
<cfinvoke component="com.macromedia.util.calc"
method="add"
a="2"
b="4"
returnVariable="result" />
<cfinvoke method="assertEquals"
message="The add function is broken."
subject1="#result#"
subject2="6" />
</cffunction>
<cffunction name="test_adding_two_absolute_numbers"
access="package">
<cfinvoke component="com.macromedia.util.calc"
method="addAbsolute"
a="-2"
b="2"
returnVariable="result" />
<cfinvoke method="assertDoesNotEqual"
message="Signs are not being removed from the addAbsolute function."
subject1="#result#"
subject2="0" />
</cffunction>
</cfcomponent>
The test above would be invoked by making the following request from your browser:
http://localhost/com/macromedia/util/calc_test.cfc?method=run
How the CFUnit Testing Components Work
Because all test cases extend the test_case component, they work simply by iterating over their own set of functions and invoking any function that begins with the word "test." All test functions use one or more assert methods to test one or more pieces of data, so there is one HTML result row rendered for every assert statement called. This row indicates clearly whether the assert passed or failed and gives the test function's name and other useful information.
The test_suite component simply keeps an array of test cases. When you call "run" on the test suite, it iterates through the array of test cases and runs each test case.
Take a look through the source code for case_case.cfc and test_suite.cfc (which are part of DevNet Resource Kit Volume 3) to get a better idea of how they work. Write a couple of simple test cases to get a feel for them. Once you become accustomed to validating and verifying your work though unit testing, you'll find it difficult to work without them.
About the author
Christian Cantrell is the Macromedia Server Community Manager. He has been developing large-scale, web-based applications in ColdFusion, Java, JSP, and Macromedia Flash for the last five years. He is the author of numerous tutorials and whitepapers, and is coauthor of Flash Enabled: Flash Design & Development for Devices. Keep up with Christian by reading his blog.
Submit feedback on our tutorials, articles, and sample applications.