OCCASIONAL CF NEWSLETTER
February 2000
1. Fusebox and Test Harnesses
(This first item deals with working within a Fusebox development methodology framework. If you're not familiar with Fusebox, you may want to check out the docs at www.fusebox.org and on my site at www.teamallaire.com/hal.)
Usually, discussing testing produces more heat than light with lots of admonitions and sermons. There are many different types of tests that test professionals identify. I want to talk about unit testing -- the testing of individual code pages. And I hope to provide some concrete help with this.
If you're working with the Fusebox development methodology (www.fusebox.org), you'll be generating lots of small code files. While some people don't like this aspect of Fusebox--I have yet to figure out the reason for this objection--it can prove very valuable in unit testing your code, especially if you're using Fusedoc as a documentation standard. Fusedoc is also helpful in tracking application development as I show in the upcoming April issue of "ColdFusion Developer's Journal" that details how to use Fusedoc to create a database of fuses and variables.
The Fusedoc tells me what variables to expect and what variables I'm on the hook to create. Since I know the variables to expect, I can write a "test harness" that sets these variables and then includes the appropriate fuse. Let's say I wrote a fuse called "actAddItemToCart.cfm" that adds an item to a shopping cart. Its Fusedoc looks like:
<!---
|| Responsibilities: I accept an itemID and an itemQty and add this
to the user's shopping cart (identified by userID).
||
PageType: act
CodeLevel: 2
FuseAuthor: hal.helms
FuseStatus: in process
Edits:
||
--> RFA.completion: a FUSEACTION to return to the fusebox when all done
--> itemID: a valid PK from Products table
--> itemQty: an INTEGER
<-- qryResults: 0 or 1
<->
+++ qryInsertItemIntoCart.cfm
++> userID: a COOKIE valid PK from Users table
<++
||
END FUSEDOC--->
This Fusedoc tells me that I can expect three incoming attributes style variables, one outgoing variable called "qryResults", a cookie variable, and the fuse requires the presence of a file called "qryInsertItemIntoCart.cfm". (Additionally, all my fuses expect the presence of a variable called "self", a local variable that always points to the fusebox.) Knowing this, I construct a test harness that looks like this:
<cfscript>
self = "ShowAttributes.cfm";
RFA.completion = "";
itemID = "9999"
itemQty = "2"
</cfscript>
<cfcookie name="userID" value="halh">
<cfinclude template="actAddItemToCart.cfm">
I save this file as tstActAddItemToCart.cfm and when I want to test my code, I can run this file. Note that I set "self" to "ShowAttributes.cfm". This file just takes any form or URL variables and prints them out onto the screen so that I can see what is being passed by the fuse. In this way, I can check to see that qryResults is being returned and that its returning what I intend it to.
This method of unit testing gives us confidence throughout the development process that the code we're producing works as intended. Contrast this with the more common method of waiting until the application is fully running to begin to test.
One other thing: since Fusebox doesn't use the application.cfm file, you can use this for setting things like global variables that all fuses require. Since the test harness is called by itself (and not through the fusebox), application.cfm will fire and the variables set there will be accessible. When all unit testing is complete, you can safely delete all "tst" files as well as application.cfm.
2. Understanding Arrays
I was recently working with a ColdFusion developer who was struggling understanding arrays. I thought a closer look at arrays might be helpful. Arrays can be very handy, but are sometimes thought of as too advanced for junior developers. I hope to correct this impression. What follows is an exploration of ColdFusion arrays. I concentrate mainly on two dimensional (2D) arrays. My approach is not the only way of understanding arrays, but I have found it helpful.
We start with single dimensional arrays. You can think of this as a single row from a spreadsheet. It can have as many columns as you wish.
<cfset anArray = ArrayNew(1)>
<cfset anArray[1] = "Ludwig">
<cfset anArray[2] = "Beethoven">
<cfset anArray[3] = "German">
<cfset anArray[4] = "Romantic">
This creates something like this:
Ludwig
Beethoven
German
Romantic
You can directly dereference the contents of an array "cell":
#anArray[3]# yields "German"
You can add to an array with the ArrayAppend command.
<cfset ArrayAppend(anArray,"1770-1827")>
And you can find out how many cells your array has:
#ArrayLen(anArray)#
We can loop over this array to get information out of it.
<cfloop from=1 to=#ArrayLen(anArray)# index=i>
#anArray[i]#
</cfloop>
What, though, if some Phillistine insists on adding another composer to the list? Two-dimensional arrays have more than one "column", letting us add the minimalist composer, Philip Glass, to our array. We can start with code very similar to what we used for our 1D array.
<cfset anArray=ArrayNew(2)>
<cfset anArray[1][1] = "Ludwig">
<cfset anArray[2][1]= "Beethoven">
<cfset anArray[3][1] = "German">
<cfset anArray[4][1] = "Romantic">
And you can directly dereference a "cell":
#anArray[3][1]# yields "German"
You can think of the first index of the array as referring to the "row" and the second index the "column".
What will happen if we try to use the ArrayAppend command on a 2D array?
<cfset ArrayAppend(anArray,"1770-1827")>
We get an error. We can, though, refer to the column number we want to append:
<cfset ArrayAppend(anArray[5],"1770-1827")>
If we want to, we can add Philip Glass with this code:
<cfset ArrayAppend(anArray[1],"Philip")>
<cfset ArrayAppend(anArray[2],"Glass")>
<cfset ArrayAppend(anArray[3],"American")>
<cfset ArrayAppend(anArray[4],"Mimimalist")>
<cfset ArrayAppend(anArray[5],"1937-")>
This will create an array that looks like this:
Ludwig Philip
Beethoven Glass
German American
Romantic Mimimalist
1770-1827 1937-
How large is the array? That depends on what direction you can to measure. If we use this code:
#ArrayLen(anArray)#
it will return the number of rows, just as it did when we were working with a 1D array. But we can also find out how many columns we have:
#ArrayLen(anArray[4]# will yield 2.
However, you should also be aware that arrays can be "unbalanced"--that is all row need not have the same number of columns, and all columns need not have the same number of rows. So you could create an array that looked like this:
A1 B1 C1
A2 B2
B3
Where would you use an array? Once you get used to how arrays work, you're likely to find plenty of uses for them. As an example, I recently worked on an ecommerce site that sold "packages" of items to a user. A package might contain between 3 and 15 items. Some items the user could substitute for; others could not be substituted.
Because of the complex logic involved, I decided to wait until the actual order to make the substitutions. In the meantime, I wanted to know the SKU for the item the user no longer wanted, the SKU for the item s/he wanted to replace it with, the item type, and the price difference.I used a 2D array for this, so that each "row" represented all the info I needed to know about each substitution the user wanted to make.
Once you are comfortable with how ColdFusion arrays work, you're likely to use them quite often.
|