We're speaking at
CFUnited 2008:
CFUnited - The Premiere ColdFusion Technical Conference

Search

Calendar

SunMonTueWedThuFriSat
    123
45678910
11121314151617
18192021222324
25262728293031

Subscribe Enter your email address to subscribe to this blog. You'll receive an email when we write a new post.

Recent Entries Come On In, Rails-The Water's Warm
Shan's Simple Examples: File uploads with Flex and ColdFusion

Recent Comments Google Calendar API - Creating a new Calendar with ColdFusion
Steve Julian said: When and where are you going to post the finished CFC's ? Thanks [more]

Three Phases of Programmer Development
Pat Branley said: I normally think of those phase 2 people as 'programmers' and the phase 3 people as 'developers'. I... [more]

New Job Title: Front End Engineer
Sean Corfield said: Well, there's always the excellent Fusion Authority Quarterly Journal... [more]

Down To The Wire: HTTP Sniffers
Brian M said: I second the mention of the Charles Web Debugging Proxy that Tariq mentioned. It is fantastic. It s... [more]

New Job Title: Front End Engineer
Patrick said: Heya Sean. Good point. I never understood how they did things over there at SysCon, and I understand... [more]

Archives By Subject Business of Software (4) [RSS]
ColdFusion (318) [RSS]
Conferences (6) [RSS]
Databases (87) [RSS]
Flex & Flash (109) [RSS]
Fusebox (87) [RSS]
General Development (29) [RSS]
Google (9) [RSS]
Hardware (5) [RSS]
JVM & Java (132) [RSS]
Linux (20) [RSS]
Miscellaneous (254) [RSS]
Performance (8) [RSS]
SeeFusion (36) [RSS]
Shan's Simple Examples (7) [RSS]
User Interface (3) [RSS]
Windows (5) [RSS]

Archives By Poster Daryl Banttari (10)
Nat Papovich (29)
Patrick Quinn (36)
Shannon Hicks (22)
Steve Nelson (21)
Tyson Vanek (3)


bottom corner

Evils of Global Variables when Unit Testing

Let's jump right to some code.

<cffunction name="somefunction">
   <cfargument name="email">
   <cfset var getsomething="">
   
   <cfquery name="getsomething" datasource="mydsn">
      select *
         from sometable
         where email='#arguments.email#'
   </cfquery>
   <cfreturn getsomething/>

</cffunction>

versus:

<cffunction name="somefunction">
   <cfset var getsomething="">
   
   <cfquery name="getsomething" datasource="mydsn">
      select *
         from sometable
            where email='#session.user.getEmail()#'
   </cfquery>
   <cfreturn getsomething/>

</cffunction>

They look pretty similar right? Near identical. Neither is really easier or harder to read. Performance wise, I suspect you couldn't see much of a difference. If you're thinking to yourself, "Aww geez Steve Nelson is about to go on a week long rampage about something." You would be right!!

Here's the problem that I see and it's pretty simple. Unit Testing.

The first one is dead simple to create a unit test for. You cfinvoke the method, pass in an email. Boom! You're done. Hell you could write a bunch of different unit tests in a matter of minutes to try out different types of email addresses. Maybe throw in some Japanese kanji just to see what happens. Maybe do a SQL injection test since I didn't use cfqueryparam in my example. Then setup your testing process to continually test them with a test harneness runner.

The second function is dramatically more difficult to unit test. You can try the same thing, cfinvoke the method (no arguments). Damn. Error. It doesn't know what session.user is. So now you have to create another CFC. (Which you were going to do anyway right? I guess we need to unit test that one too) Create the CFC with at least one method getEmail. Great. Run the first test it works. Beautiful. But now let's try our Japanese kanji test. How do we change that value returned by getEmail? We have two choices. The obvious choice is to edit the original getEmail method and have it return the static Japanese kanji string. Fine. But now you're no longer testing the english string. So the better choice is to create ANOTHER method called updateEmail which modifies the email address returned. Then the second unit test has to call the updateEmail() before it calls getEmail(). Great it works! Now run the first test again... aw damn it! It's still displaying the kanji. What the heck? Oh right, the first one ALSO has to call the updateEmail() method because it's a session scope variable. The Session scope is a persistent global variable. One unit test affected another unit test when global variables are used.

Did I lose you? Good. I was trying. Chew on that a bit. I'll come back to some unit testing code soon. Personally I prefer passing in every argument my method needs.

-Steve Nelson

By the way, if you're looking for CF Architects or simply some extra CF hands, send us an email or give us a call at +1-970-223-2278. We're about to finish up a few projects and will have some free time shortly.

Related Blog Entries

Comments
You know, you should put cfqueryparam in the sql. I think there is a security problem by using single quotes like that.
# Posted By Jim Thomas | 1/23/08 7:20 AM
This is less about global variables per-se, and more about encapsulation and the Law of Demeter (http://en.wikipedia.org/wiki/Law_of_Demeter). When something is difficult to test, it can be a "code smell" that indicates a design deficiency (as in this case where the method is relying on external data).
# Posted By Brian Kotek | 1/23/08 8:34 AM
A "code smell" haha I like that term. I'll give that wiki entry a read.

I guess the big question is how do you determine if something is 'difficult' to test? 'Difficult' is so relative it seems like it would be a useless comparison. no?
# Posted By Steve Nelson | 1/23/08 9:09 AM
That's an interesting article. I guess I'm suggesting going one step further. In other words if the Law of Demeter states that one dot is better than two dots (i.e. a.Method() is better than a.b.Method()) then I'm suggesting that No dots is better than one dot.

Simple arguments (strings, numbers etc) are less difficult to test than complex arguments (objects).

Therefore the code smells like flowers instead of garbage. haha
# Posted By Steve Nelson | 1/23/08 9:17 AM
No the "one dot" thing is to reduce the chaining of method calls, which increasing coupling because when I do obj.getFoo().getBar(), my calling code is now coupled to the fact that obj is holding a foo and that foo has a method called getBar(). I'm coupled to obj, which I want to be since I'm calling it, but I'm also coupled to the internal implementation of obj as well as to foo. Yucky.

But beyond that, the real point of the Law of Demeter is this:

A method M of an object O may only invoke the methods of the following kinds of objects:

1. O itself
2. M's parameters
3. any objects created/instantiated within M
4. O's direct component objects

You example of referencing the session scope violates these rules, and a result of that violation is that the component is harder to test compared to your version where you pass in the value (now the method is using a parameter, which as you can see above is in line with the rules).
# Posted By Brian Kotek | 1/23/08 9:27 AM
Oh I see what you're talking about now wrt the session scope. But is that really the same thing? You're basically saying that the session scope (a structure afaik) is effectively an object therefore by referencing objects in the session scope is breaking the law of demeter. But that would then say that referencing simple values (strings, numbers) in the session scope is ok.

Yuck, that has a bad code smell to it to me. I'm not convinced this law of Demeter really applies to 'scopes' in CF. Does it?
# Posted By Steve Nelson | 1/23/08 10:40 AM
Yes, it does. Any scope outside of the object should be off limits. Simple values are not scopes. The point is that if the method references the session scope, it is referencing something that is not part of the object or a parameter passed into the method. That is why your second example is easier to test than the first, because in the second example the method is using a parameter passed into the method, and it has no idea that the session scope even exists.
# Posted By Brian Kotek | 1/23/08 10:47 AM
Ok fine. Then by that logic, you should never pass an object, as an argument, into a function because you're then referencing the arguments scope! I agree completely!

That's right Brian said it. Not Steve! haha :-)
# Posted By Steve Nelson | 1/23/08 11:34 AM
I said "any scope outside of the object". The arguments scope is, by definition, internal to the object (actually even further, it's internal to an individual method). The same applies to the variables scope (instance data) of a component. It is the external scopes that should be of concern.
# Posted By Brian Kotek | 1/23/08 1:44 PM
Haha. I can't get one by you Brian.

Hey, are you coming to cfunited? Are you speaking?
# Posted By Steve Nelson | 1/23/08 7:09 PM
I'm speaking at CFObjective and am the Frameworks track chair at CFUnited, but it doesn't look like I'll be speaking, just trying to help things run smoothly.

BEER.

:-)
# Posted By Brian Kotek | 1/23/08 9:48 PM

bottom corner