Entries for month: August 2008
My Approach to Test Driven Development Part 2 - MXUnit, Coldspring and ColdMock
Posted by Paul Marcotte | Tags: MXUnit , Test Driven Development , ColdSpring
Like the title says, this post will include code samples for setting up an MXUnit test case using ColdSpring and ColdMock. The goal of any test case are to isolate the component under test as much as possible. I find ColdMock to be a simple and powerful tool for achieving test isolation. For this example, I created a test case for my ConfigFactory which I mentioned in a previous post. TheConfigFactory component has a constructor dependency on the Environment Config component developed by Rolando Lopez which makes this a good case for mocking.
A common convention for test cases is to add the suffix "Test" to match the test case to the component being tested. I have my own convention for setting up tests withColdSpring bean definitions which I keep in a similarly named xml file. All test cases extend the BaseTestCase which contains a couple of methods to simplify test configuration with ColdSpring. The full code of the of the BaseTestCase follows.
<cfcomponent displayname="tests.BaseTestCase" extends="mxunit.framework.TestCase" output="false">
<cfset variables.beansXML = "">
<cffunction name="setBeanFactory" access="private" output="false" returntype="void">
<cfargument name="beansXML" type="string" required="true">
<cfargument name="params" type="struct" required="false" default="#structnew()#">
<cfscript>
if ((not structkeyExists(request,"beanFactory")) or (comparenocase(variables.beansXML,arguments.beansXML) neq 0))
{
variables.beansXML = arguments.beansXML;
request.beanFactory = createObject("component" ,"coldspring.beans.DefaultXmlBeanFactory").init(StructNew(),arguments.params);
request.beanFactory.loadBeans(variables.beansXML);
}
</cfscript>
</cffunction>
<cffunction name="getBeanFactory" access="private" output="false" returntype="any">
<cfreturn request.beanFactory>
</cffunction>
</cfcomponent>
In the past I kept my ColdSpring reference in variables scope, but I found that when testing components based that rely on Transfer ORM, I can shave the time of tests considerably when I have multiple tests in the same test case. Whether you run the MXUnit HttpAntRunner, the eclipse plugin or a manually configured test suite within a browser they all run as a single request, so as you define more tests it helps to speed things up.
Here's what the ConfigFactoryTest.xml looks like.
<beans>
<bean id="mockFactory" class="tests.MockFactory" singleton="true" />
<bean id="EnvironmentConfig" factory-bean="MockFactory" factory-method="createMock">
<constructor-arg name="objectToMock">
<value>model.Environment</value>
</constructor-arg>
</bean>
<bean id="ConfigFactory" class="model.ConfigFactory">
<constructor-arg name="hostName">
<value>${hostName}</value>
</constructor-arg>
<constructor-arg name="environmentConfig">
<ref bean="EnvironmentConfig" />
</constructor-arg>
</bean>
</beans>
You can see how easy it is to mock the dependency using the ColdMock MockFactory. Below is the full code for my ConfigFacotry test case. The important thing to note is that the component display name is used to resolve the path to the ColdSpring bean definitions used in the test suite.
<cfcomponent displayname="tests.ConfigFactoryTest" extends="tests.BaseTestCase">
<cffunction name="setUp" access="public" returntype="void">
<cfset var beanDefFileLocation = expandPath('/' & Replace(GetMetadata(this).displayname,'.','/','all') & '.xml')>
<cfset var params = Structnew()>
<cfset params.hostName = "www.somedomain.com">
<cfset setBeanFactory(beanDefFileLocation,params)>
</cffunction>
<!--- Begin Specific Test Cases --->
<cffunction name="testGetSetting" access="public" returntype="void">
<cfscript>
var configFactory = "";
var env = getBeanFactory().getBean("EnvironmentConfig");
var settings = structNew();
settings["MyString"] = "my string";
env.mockMethod('getEnvironmentByUrl').returns(settings);
configFactory = getBeanFactory().getBean("ConfigFactory");
assertTrue(configFactory.getSetting("MyString") eq settings["MyString"]);
</cfscript>
</cffunction>
<cffunction name="testGetAllSettings" access="public" returntype="void">
<cfscript>
var configFactory = "";
var env = getBeanFactory().getBean("EnvironmentConfig");
var settings = structNew();
settings["MyString"] = "my string";
env.mockMethod('getEnvironmentByUrl').returns(settings);
configFactory = getBeanFactory().getBean("ConfigFactory");
assertTrue(StructCount(configFactory.getAllSettings()) eq 1);
</cfscript>
</cffunction>
<cffunction name="testOnMissingMethod" access="public" returntype="void">
<cfscript>
var configFactory = "";
var env = getBeanFactory().getBean("EnvironmentConfig");
var settings = structNew();
settings["MyString"] = "my string";
env.mockMethod('getEnvironmentByUrl').returns(settings);
configFactory = getBeanFactory().getBean("ConfigFactory");
assertTrue(configFactory.getMyString() eq settings["MyString"]);
</cfscript>
</cffunction>
<!--- End Specific Test Cases --->
</cfcomponent>
Most of my tests aren't this involved. Since my ConfigFactory has a constructor dependency on EnvironmentConfig, I pull it out of ColdSpring first, mock the getEnvironmentByUrl method to return a known structure before requesting the ConfigFactory. Truly powerful stuff!
I have attached a zip file of this sample test bed as an enclosure for folks to try out.
My Approach to Test Driven Development Part 1 - Application Structure and Apache
Posted by Paul Marcotte | Tags: Apache , Test Driven Development
As I delve deeper into Test Driven Development (TDD), I have refined my development setup in order to feel confident that my tests are relevant and that they provide immediate feedback about the status of my working code. The two most important goals that I have tried to accomplish with my revisions are to follow the credo of never committing broken code and to isolate and reset the database to ensure the integrity of test data. The following examples are not solely related to TDD, but represent an accumulation of development "best practices" gathered from many sources. I'm going to admit straight up that I am not a TDD purist. I almost always write model components before tests. But I always test before running code on the client side. One of the biggest selling points about unit testing and for me is that I can debug faster should a problem arise and I can do it repeatedly and confidently. Working this way makes me feel somewhat pragmatic. I'm willing to spend a known amount of time setting up and running tests so that I can avoid spending an unknown amount of time debugging. First up, my application directory structure and Apache virtual host settings.School's Out...Forever!
Posted by Paul Marcotte | Tags: Miscellany
Today, I wrote the final exam for the final course I need for my BComm. It was fitting that I wrote it at my home town University in Windsor, Ontario. I live in BC now, but circumstance brought me to Windsor this week, so I made arrangements to write my exam in town. Before the exam, I had that "full circle" feeling walking around the campus I had not set foot on for nearly 20 years. For a little history, I started university in September 1985 and left school in February 1989 to pursue personal interests (of the rock and roll variety). After many joe jobs and many bands, I made the decision to return to school in 1998 as a distance education student. Now ten years later, I've completed the requisites for my degree. It's been a long strange trip and now that it's over I just have to crank up the Alice Cooper...