Tuesday, September 21, 2010

Unit Testing Custom Tags with MXUnit

Introduction

We love MXUnit at work and at Mach-II to unit test our components and to do integration testing.  In Mach-II Simplicity (1.8), a big feature was our form and view custom tag libraries.  These features made developing the UI (view) layer of your application easier and less prone to error.  Why would you write this kind of form with manual data binding:


<form method="post" encType="multipart/form-data" action="#BuildUrl('processUser')#">
        <h4>First Name</h4>
        <p><input id="firstName" value="#event.getArg('user').getFirstName()#" type="text" name="firstName"/></p>
        <h4>Last Name</h4>
        <p><input id="lastName" value="#event.getArg('user').getLastName()#" type="text" name="lastName"/></p>
        <p><input type="submit" value="Save Changes" /></p>
</form>

When you could just write this and have auto binding:


<cfimport prefix="form" taglib="/MachII/customtags/form" />
<form:form actionEvent="processUser" bind="user">
        <h4>First Name</h4>
        <p><form:input path="firstName" /></p>
        <h4>Last Name</h4>
        <p><form:input path="lastName" /></p>
        <p><form:button value="Save Changes" /></p>
</form:form>

Our First Pass Testing Solution and Failures

What this brings up is that a lot of complex logic is now inside of custom tags*.  How do you test custom tags?  For the most part, a lot of the logic in custom tags should be about output and not data manipulation.  MXUnit makes it easy to compare data if the results are simple values, structs, arrays, queries, etc.  However, the output from a custom tag is not just a simple string like "abc123" but more a complex string with nesting of tags and relationships between tags (for example a "select" and "option").

When we first started down this path of testing our custom tag libraries, we started by doing straight up text comparisons:


<form:form actionEvent="something" bind="${event.user}">
            <cfsavecontent variable="output"><form:input path="firstName" /></cfsavecontent>
</form:form>
<cfset assertTrue(output EQ '<input id="firstName" value="#event.getArg('user').getFirstName()#" type="text" name="firstName"/>') />

This was simple but lead to an easy break down down the line.  There were two sticking points:

1) Our custom tag library has a "tag writer" relies a lot on looping over a struct of tag attributes .  As we all know in CFML, we cannot rely on the order of iteration of a struct.  So while assertion works on OpendBD it sometime failed on ACF8 because we were relying on the custom tag to ouput the tag attributes in the same order as our assertion string.  In all reality, the browser does not care about the order of the tag attributes and neither should our unit test.  The flexibility of the unit test was strongly coupled to string as a whole; not the individual parts.

2) It is hard to test relationships such as <select> and <option> tags in straigh string comparison.

2) Maintaining the unit tests becomes painful because of the first and second point.

These limitations were all deal breaker after just a week of trying to get tests to run all the time.  So on to find another solution that is less coupled to the straight string comparisons.

Our Second Pass Testing Solution and Failures

Ok, let's test individual parts like the value of an attribute using RegEx or Find() built-in functions:


<form:form actionEvent="something" bind="${event.user}">
            <cfsavecontent variable="output"><form:input path="firstName" /></cfsavecontent>
</form:form>
<cfset assertTrue(Find("value="farrell"', output)) />
<!--- More assertions --->

This works, but it is still extremely clumsy having to check for each attribute and we still cannot check the parent child relationships of tags like <select> and <option>.

Our Third Pass Testing Solution and Ultimate Successful Approach

How do you solve this predicament? We "sort of" solved problem #1, but #2 and #3 are still unsolved in my book.  Let's think about what the custom tag is outputting.  Hmm...our custom tag library output XHTML compliant code and there are many tools available to use to leverage XML .  So for all of you who think XML is bad, wrong and decitful -- it's time to eat your words. Luckily for us, MXUnit has a built-in assertion called assertXPath() that fits this use case perfect.

I know you're tired of me jabbering about how we got here so you'll want to see some code right?  Bare in mind that the Mach-II custom tags do some interaction with what is happening in a Mach-II request so we had fun setting up a fake request so the object required by the tag are in memory.  For the sake of brevity, I'm going to leave that setup code out of my examples however you can always see them in the Mach-II SVN repository (the joys of open source) if you want to learn more.

Let's look at testing a simple text input:


<cffunction name="testInput" access="public" returntype="void" output="false"
    hint="Test basic 'input' tag.">

    <cfset var output = "" />
    <cfset var xml = "" />
    <cfset var node = "" />
    <cfset var bean = CreateObject("component", "MachII.tests.dummy.User").init() />
    <cfset var event = variables.appManager.getRequestManager().getRequestHandler().getEventContext().getCurrentEvent() />

    <!--- Add data to the the bean and set to the event so we can do binding --->
    <cfset bean.setFavoriteColor("red") />
    <cfset bean.setLastName("Farrell") />
    <cfset event.setArg("user", bean) />

    <cfsavecontent variable="output">
        <root>
            <form:form actionEvent="something" bind="${event.user}">
                <form:input path="favoriteColor" />
                <form:input path="lastName" />
            </form:form>
        </root>
    </cfsavecontent>

    <cfset xml = XmlParse(output) />
    <cfset debug(output) />

    <cfset node = assertXPath('/root/form/input[@type="text" and @value="red" and @id="favoriteColor"]', xml) />
    <cfset node = assertXPath('/root/form/input[@type="text" and @value="Farrell" and @id="lastName"]', xml) />
</cffunction>

All XML documents require a "root" node of some sort.  In HTML, it's the <html> tag so in our unit test we just used <root> so XMLParse() wouldn't choke on the code.  We also want to check that the "id" attribute and the "value" attributes are what is expected and we can easily do that in our XPath assertion string.

Ok, so that's a simple example.  What about something more complex?  Let's look at how we test our <form:radiogroup> tag.  This tag takes a collection (lists, struct, array, array of structs or queries) and turns them into a group of radio buttons based off a template:


<cffunction name="testRadiogroupWithQueries" access="public" returntype="void" output="false"
    hint="Test basic 'radiogroup' tag.">

    <cfset var output = "" />
    <cfset var xml = "" />
    <cfset var node = "" />
    <cfset var bean = CreateObject("component", "MachII.tests.dummy.User").init() />
    <cfset var event = variables.appManager.getRequestManager().getRequestHandler().getEventContext().getCurrentEvent() />
    <cfset var colors = QueryNew("v,l") />

    <!--- Add data to the the bean and set to the event so we can do binding --->
    <cfset bean.setFavoriteColor("red") />
    <cfset event.setArg("user", bean) />

    <!--- Test with simple array --->
    <cfset QueryAddRow(colors) />
    <cfset QuerySetCell(colors, "v", "red") />
    <cfset QuerySetCell(colors, "l", "Big Red") />
    <cfset QueryAddRow(colors) />
    <cfset QuerySetCell(colors, "v", "green") />
    <cfset QuerySetCell(colors, "l", "Giant Green") />
    <cfset QueryAddRow(colors) />
    <cfset QuerySetCell(colors, "v", "brown") />
    <cfset QuerySetCell(colors, "l", "Bad Brown") />

    <cfsavecontent variable="output">
        <root>
            <form:form actionEvent="something" bind="${event.user}">
                <form:radiogroup path="favoriteColor" items="#colors#" labelCol="l" valueCol="v">
                    <label for="${output.id}">${output.radio} <span>${output.label}</span></label>
                </form:radiogroup>
            </form:form>
        </root>
    </cfsavecontent>

    <cfset xml = XmlParse(output) />
    <cfset debug(node) />
    <cfset debug(output) />

    <cfset node = assertXPath('/root/form/label/input[@type="radio" and @value="red" and @id="favoriteColor_red" and @checked="checked"]', xml) />
    <cfset node = assertXPath('/root/form/label/input[@type="radio" and @value="green" and @id="favoriteColor_green"]', xml) />
    <cfset node = assertXPath('/root/form/label/input[@type="radio" and @value="brown" and @id="favoriteColor_brown"]', xml) />
    <cfset node = assertXPath('/root/form/label[@for="favoriteColor_red"]/span', xml, "Big Red") />
    <cfset node = assertXPath('/root/form/label[@for="favoriteColor_green"]/span', xml, "Giant Green") />
    <cfset node = assertXPath('/root/form/label[@for="favoriteColor_brown"]/span', xml, "Bad Brown") />
</cffunction>

As you can see, we setup some test data and pass that to the "items" attribute of the <form:radiogroup> custom tag.  The tag doesn't know which columns to use for the value or for the label so we indicate that.  Let's take a closer look at the template that is being used:


<label for="${output.id}"><span>${output.label}</span> ${output.radio}</label>

This template is interated over (i.e. looped over) for each item in the query.  We use a simple placeholder syntax of ${} to indicate where the radio and label are outputted (other computed attributes like the "id" are also available).  Our assertations in this case not only test for tags heirarchy and tag attributes, but the inner text of tags.  In this case, we need to test that the value of "${output.label}" that is displayed is correct (not the actual <label> tag).  We wrapped a <span> around it so we can easily find it with our XPath assertion.  Also, when testing for the "${output.label}" we need to make sure we're grabbing the right one so the XPath searches that the <span> is inside of a <label> with the correct "for" attribute.

In Closing

Testing custom tags with MXUnit is easy if you figure out the right plan of attack and after three iterations to our testing strategy -- we found one that works.  I'd be happy to hear if people have suggestions on improving how custom tag output can be tested with MXUnit, however at this point I think this solution is rather slick and leverages XML/XPath without the need for any custom assertions to be written in MXUnit.

Now no code should go un-tested including custom tags.  This is especially true in something like Mach-II which is a community asset and having unit tests makes the software a better product.  So now go forth and unit test your custom tag libraries!

* Sidebar: For performance, we used straight up custom tags with a "UDF"-like function library instead of having custom tags call CFCs.  Our basic testing at the time showed that CFCs were 10-15 times slower than the custom tag with "UDF"-like function library (included via a cfinclude).

Thursday, September 16, 2010

YAF - Yet Another Framework: Giftware Versus Open Source

With the seasons changing and Fall fast approaching, it must be time for yet another framework (YAF) to show signs in the CFML world.  I'll give credit for this post to a tweet from Matthew Reinbold:

Playing with new ColdFusion framework that still is in stealth mode. Do programmers need another MVC/ORM set of scaffolding? Let's find out.

Before I take issue with having another framework pop up in the CFML world and why I feel that it is possibly detrimental to our small sect of programmers, I want take issue -- moreover -- more notice to the word "stealth" in the tweet.  What bothers me there is a big difference between developing a framework for internal / personal use versus public consumption.  If I was developing an internal "framework" for my personal use, having input from possible users of my "framework" really would not matter.  I would be using it for my own needs; Programmer Paul doesn't need to put his own two cents on how feature Z should work.  On the flip side, developing a framework for public consumption in a vacuum (i.e. "stealth" mode) is less than beneficial to all parties that possibly would use the framework.  Therefore if Programmer Paul had interest in what I was doing, his knowledge can only make the "framework" better.  This means that public interaction, even if it's just one person, is beneficial.  You can't pay somebody enough to give you bold and honest views on stuff.  This begs the question: How do you make somebody care?

Developing transparently is key to building an user base.  Without transparency in a project, you cannot call yourself an open-source project.  I've found out in that most developers consider the main tenant of open source is that the code is freely available (i.e. the "license").  Giving users the software for free where the software is developed in a vacuum and there is limited means or encouragement to contribute back to the project is merely giftware*. Open source is much more than the license; it's about philosophy on how software should be developed and a community that rallies behind it.

The big different, other than transparency in developing software in the open, is that interested parties will help you develop new features if you let them be "shareholders" of the project.  As with anything in life, nobody cannot care about everything.  Humans weren't designed to function that way.  We make hundreds of decisions everyday on what we spend our time on, what is important to us and what we do so we are happy individuals.  This is no different in open source software.  Consider this situation:

If Developer Dave uses Project Perfect and finds a Big Bug, what encourages Developer Dave to contribute his fix back to the Perfect Project instead of just working the next Alluring Application that will make millions when it hits the world?

The answer to this is: nothing.  Unless you are financially tied to open source to make a living, most developers don't count themselves shareholders in the project because they don't own it.  Whereas if the project  helps you succeed at your job, there is a reason to become involved in some of these projects.  This is where being transparent, making people's opinions count and accepting (acceptable / valid) contributions into a project makes everybody in your user base a potential shareholder and champion of your project.  Making this conversion of your user base to shareholders is something that needs to be cultivated from the start of the project.  It's nearly impossible to make conversions when your project starts in "stealth" mode because it sets the "tone" of the project from the start.  Transparency is the biggest reason in my book why 99% of all CFML "open source" projects are just giftware and will always be one-man operations.  Giftware is just viewed by "Developer Dave" as a free tool where open source is more -- it's a community of like minded folks using the tool.  All in all, the side effect is a better software.

Therefore if you make philosophy and transparency #1 on your list, will you see your project succeed (provided it's a good tool too).  Otherwise, you'll just be YAF that gets forgotten in 6 months time.

* I want to attribute the term "giftware" to by good friend Matt Woodward.  I heard it applied by him first so I can't take credit on the term.

Tuesday, September 7, 2010

Last call for BFusion/BFlex 2010 Registration - Best & Cheapest Conference Around - 9/11/-9/12 Bloomington IN

BFusion/BFlex 2010
Hands-on training for developers and designers from industry leaders coming to IU. You will not find a more effective and cost-effective professional development opportunity.
 
Want to learn more about ColdFusion and Adobe Flex? What about cross-platform AIR applications? Already handy with Illustrator and Photoshop and want to quickly turn your work into Rich Internet Applications? Whether you are a curious beginner or an advanced developer, BFusion (a full day of ColdFusion training) and BFlex (a full day of Flash Platform development) have something for you:



  • What: Hands-on training from the experts in ColdFusion, Flex, AIR, Catalyst, and other technologies

  • When: September 11 and 12 (Saturday and Sunday)

  • Where: IU Bloomington

  • Cost: $30 for one day, or $45 for both days (price includes lunch)



Designers: BFlex also includes an all-day session for you (no programming necessary), Flash Catalyst - From Design to Rich Internet Applications without Coding.

For more information about the sessions, speakers, and registration,  please see:  http://bflex.indiana.edu

See this post from one of our speakers Matt Woodward, Principle It Specialist of the US Senate, about his take on this year’s event: http://blog.mattwoodward.com/get-your-hands-dirty-at-bfusionbflex.

The event is also listed on Facebook and LinkedIn. Please share it with friends and colleagues.

On Facebook: http://www.facebook.com/home.php#!/event.php?eid=108657742524082
On LinkedIn: http://events.linkedin.com/BFlex-BFusion-2010/pub/4046

Thursday, August 26, 2010

Technical Debt: If You Can't Handle Change, It's Time To Change Careers

Change happens every moment. Right now things have changed just from the second before. This means all things in life are fleeting. Not changing is like trying to hold on to the present as it becomes the past. Why should we hold on to the past? We should not. Nobody has every succeeded at this and you should not think that you're special enough to think you will be the first.


Change is good. It should not have negative thoughts connected with it. Change will happen whether we like it or not.  We might as well learn something new so we can adapt to change in ways that do not cause us suffering.  The scariness of change can be counteracted with learning. Learning something will lessen any suffering that occurs from change.  In the terms of IT, you'll live a happier IT existence because you will not accumulate massive amounts of "technical debt" by not changing and therfore not learning.  Doing things in the "tried and true" methods don't always mean you're doing it right.


So take the plunge and make a nice down payment on the change that inevitably comes. Learn something new every day.  Or if you want a different explanation in clearer wording: How can you be in IT if you don't want to learn new stuff? If you don't want to learn, you're in the wrong business.

Wednesday, August 11, 2010

CFML Function - createDatetimeFromHttpTimeString()

Working with HTTP time strings should be easier to CFML / ColdFusion,
but there is no built-in function that will create a CFML date/time from
a UTC HTTP time string. So here is the UDF that can help you work with
HTTP time string in native CFML date/time functions. I didn't check
cflib.org first, but I really couldn't check it out of professional
courtesy because I was writing this function for open source project and
I didn't want to deal with attribution / license stuff (yes, I'm lazy).





<cffunction name="createDatetimeFromHttpTimeString" access="public" returntype="date" output="false"
    hint="Creates an UTC datetime from an HTTP time string.">
    <cfargument name="httpTimeString" type="string" required="true"
        hint="An HTTP time string in the format of '11 Aug 2010 17:58:48 GMT'." />

    <cfset var rawArray = ListToArray(ListLast(arguments.httpTimeString, ","), " ") />
    <cfset var rawTimePart = ListToArray(rawArray[4], ":") />
   
    <cfreturn CreateDatetime(rawArray[3], DateFormat("#rawArray[2]#/1/2000", "m"), rawArray[1], rawTimePart[1], rawTimePart[2], rawTimePart[3]) />
</cffunction>




Enjoy!

Thursday, August 5, 2010

FuseboxFramework.com Domains Are Going Away Soon...


Where did you get those URLs? The fuseboxframework.com domains are going
away soon (because no one in the Fusebox community was interested in
taking them on - and they expire in two weeks).

via groups.google.com (a comment by Sean Corfield)

This is in reference the domain being used by the what appears to be the "defunct" FuseNG project that forked from the Fusebox project. While these aren't the main fusebox.org domains, this prompted me to look at the status of the main Fusebox project.


I was surprised to learn that the last stable release for Fusebox was in March 2008 (version 5.5.1). Has it really been that long? It will be 2.5 years ago in just about a month or so without a release is stunning. Time flies.  Also, I quickly checked the Fusebox Trac site. Only one new ticket other than spam tickets has been filed or commented on in the past 6 months and that one ticket is just a question on syntax (it should have been sent to a list).  What is the state of Fusebox these days? Is TeraTech really "driving Fusebox forward and you can expect to see major improvements to the web site and the documentation in due course"? I don't see evidence of it on the site.


What really saddens me is the state of the CFML community. Have people not learned to pitch in and help their open source projects? Clearly not because there is still the glut of new one-man projects that never leave the ground or barely hover. CFML community members need to band to together instead of re-inventing the wheel. They need to learn to contribute (which I'd say that 99.9% of them do not) and realize that there is only a handful of people contributing to their open source project of choice. You don't have to pay for software with money; you can pay with your time, talent and expertise. Contributing does not always mean code but help on lists, documentation, sample applications, etc. There are so many things to do on open source project other than the next generation of code.


This is a call to arms! If you use open source, donate some time back to it or you might sadly find yourself with a defunct project and no maintainer there to help you. The great news is you can save yourself by contributing your time now. I urge all CFMLers to donate just 30 minutes a week to ONE project of your choice (if you don't know what to do, contact the maintainers -- I'm sure they have a laundry list of things to do). Just 2 hours a month would change the state of affairs in the CFML community and propel our language forward.


Monday, August 2, 2010

CounterMarch Systems Blog: Mach-II is (still) awesome



Mach-II rocks. It is one of the more "mature" surviving ColdFusion application frameworks, and as such doesn't get nearly the amount of buzz that the new (interesting, different, creative, awesome-in-their-own-way) frameworks get. If you're looking at frameworks, I encourage you to take a look at the amazingly capable Mach-II - we've had fantastic success with it!

Things I really, really like about Mach-II:

Backwards compatibility


Take an old (Mach-II 1.1 app, circa 2006) and update the framework to 1.8 (released 2009). Replace the application.cfm file with application.cfc, comment out everything in index.cfm and *boom*...you're done. I can't really assess how challenging it is for the team to maintain backwards compatibility, but I sure do appreciate how simple it is to upgrade. Instant feature add with zero headaches.

Coldspring integration


<include file="./mach-ii_coldspringProperty.xml" />

and in your listeners:

<cfcomponent name="mpListener" extends="MachII.framework.Listener"



depends="howConfigManager,mpManager,reportManager,notificationManager">

I don't think it gets much easier than that. Instant Coldspring integration for dependency injection (and more), radically simplifying the configuration of all of my listener and model cfcs. Now we've got all the power of Coldspring cleanly available to our Mach-II application.

Faster Fixes


Knowing where to find things is one of the hallmarks of any good framework. Some do it by convention, some by configuration. Mach-II falls into the latter camp but that's perfectly fine by me. Getting back into the mental model of an application months (or years) after deployment is a challenge but by simply cracking open mach-ii.xml I can see what happens where within seconds and bend it to my will. I love this.

Modularity


This wasn't in the framework back in the 1.0 and 1.1 days (when we created some mammoth apps), but by 1.5 (which came out in 2008) the ability to break up large Mach-II applications into separate modules was baked in and oh-so-handy. It's two years later and we've been able to make the easy changes to break those older apps into a collection of modularized sub-apps for much easier maintenance. Perfect example is an extranet: lots of only slightly related apps put under one common umbrella sharing security or UI components. This is easy to implement and support in a well-designed Mach-II application. All of our more recent apps make use of this feature.

The Team


The people who created and have since improved and evolved Mach-II are some of the smartest folks who are or have been involved in the ColdFusion community. Look at this list. Not only have they created something awesome, but they're incredibly responsive on the wiki, development Google group, and framework users Google group. They use this framework at their day jobs so you know that each release has been painstakingly designed, implemented, tested, re-tested, piloted and then released. Plus, they're open to new ideas (even if you personally have no idea how to make them happen!). Code is great, but the people are the best.

The View Loader


This is where some of those "convention-based" concepts have crept in to Mach-II much to the benefit of the users. Back in the "bad old days" your mach-ii.xml file would contain a block that looked sorta like this, except far longer:

<page-views>
      <page-view name="showWelcome"       page="views/showWelcome.cfm" />
      <page-view name="showHome"       page="views/home.cfm" />
      <page-view name="baseTemplate"       page="views/baseTemplate2010.cfm" />
      <page-view name="blankTemplate"    page="views/blankTemplate.cfm" />
      <page-view name="welcomeLanding"    page="views/welcome_landing.cfm" />
      <page-view name="showSendPassword"    page="views/showSendPassword.cfm" />
      <page-view name="showAppHelp"       page="views/showAppHelp.cfm" />
   (and so on)

Each page would have a page-view defined. Someone called shenanigans on it and now we have the PatternViewLoader to replace ALL of it!

<page-views>
   <!-- This would load all views with the pattern of "/views/**/*.cfm" which is the most basic and common pattern -->
<view-loader type="MachII.framework.viewLoaders.PatternViewLoader" />
</page-views>

So, so simple. Love this line of the config file more than any other.

Better apps, faster.


Take Mach-II off the shelf and use it as your "glue." Add Coldspring to manage your model. Use ColdFusion 9's new Hibernate ORM capabilities to cut the lines of code in your model by a huge factor. Redirect some of the saved time into building a better UI using jQuery, improving your cross-browser capabilities with clean CSS and making your client happier by communicating more frequently. Result: a better app, designed to be maintainable and much more in line with your client's spoken and unspoken expectations. How could any craftsman not feel good about that? Mach-II is a key component to that successful formula for us.

Try Mach-II. Need help? Talk to the team on the list. Build great things.



This exactly what Team Mach-II likes to hear. Thanks for the shout out Steve!