Contact info.
 
My Secret Life as a Spaghetti Coder
home | about | contact | privacy statement
Well, I guess I lied when I said xorBlog wouldn't be developed until I had caught up on my writing. I still haven't gotten caught up, but this morning I couldn't stand it any more - I had to have a way to categorize posts. Now, I didn't TDD these, and I didn't even put them in the right place. True to the name of the blog, I interspersed code where it was needed. I feel absolutely dirty, but I just couldn't spare the time at the moment to do it right, and I could no longer endure not having any categories. So, I took about 15 minutes, coded up a rudimentary category system, violated DRY in 2 places, and put a few comments like "this needs to be refactored into a CFC" throughout the code (as it needed).

At least I have some categories now (its not as gratifying a feeling as I thought it would be, however). I plan on refactoring this as soon as I have a chance. I'll write about it as well - it might make for some more interesting reading in the TDDing xorBlog series of posts.

Hey! Why don't you make your life easier and subscribe to the full post or short blurb RSS feed? I'm so confident you'll love my smelly pasta plate wisdom that I'm offering a no-strings-attached, lifetime money back guarantee!



I had hoped to have more functionality in xorBlog by now, such as categories and comments, and a way for each post to have its own page. However, as I've been writing the TDDing xorBlog series of posts, I've realized how much of a pain it is to write them ex post facto.

The pain comes from the fact that the code base is much more mature than what I'm writing about, so I'm having to edit a lot of the code for posting so that it matches my original thought process.

While it was good to get the blog up and running in the beginning, I'm going to catch up on the writing aspect before I add any more functionality. That way, I can write as I code. Doing that, the thoughts will be fresher and I won't have to edit the code.


Now that we can insert posts, it is possible to update, select, delete, and search for them. To me, any one of these would be a valid place to go next. However, since I want to keep the database as unchanged as possible, I'll start with test_deletePost(). This way, as posts are inserted for testing, we can easily delete them.

Here is the code I wrote in xorblog/cfcs/tests/test_PostEntity:

<cffunction name="test_deletePost" access="public" returnType="void" output="false">
   <cfset var local = structNew()>   
   <cfset local.newID=_thePostEntity.insertPost(name="blah", meat="blah", originalDate="1/1/1900", author="yoda")>
   <cfset local.wasDeleted = _thePostEntity.deletePost(local.newID)>

   <cfset assertTrue(condition=local.wasDeleted, message="The post was not deleted.")>

   <cfquery name="local.post" datasource="#_datasource#">
      select id from post where id = <cfqueryparam cfsqltype="cf_sql_integer" value="#local.newID#">
   </cfquery>

   <cfset assertEquals(actual = local.post.recordcount, expected = 0)>
</cffunction>

And the corresponding code for deletePost():

<cffunction name="deletePost" output="false" returntype="boolean" access="public">
   <cfargument name="id" required="true" type="numeric">

   <cfset var local = structNew()>
   <cfset local.result = false>
   <cftry>
      <cfquery name="local.del" datasource="#_datasource#">
         delete from post where id = <cfqueryparam cfsqltype="cf_sql_integer" value="#id#">
      </cfquery>
      <cfset local.result=true>
   <cfcatch>
   
   </cfcatch>
   </cftry>
   <cfreturn local.result>
</cffunction>

Originally, I just left the test as asserting that local.wasDeleted was true. However, writing just enough of deletePost() in xorblog/cfcs/tests/PostEntity to get the test to pass resulted in the simple line <cfreturn true>. Since that would always pass, I also added a check that the inserted post no longer existed.

Now that we have some duplicate code, its definitely time to do some refactoring. More on that next time. (To be continued...)


We left off after writing the test for the insertPost() method. Now, we're going to make that test pass by writing the code for it. First you'll need to create PostEntity.cfc in the xorblog/cfcs/src directory, and make sure to surround it in the proper <cfcomponent> tags. What follows is that code:

<cffunction name="insertPost" output="false" returntype="numeric" access="public">
   <cfargument name="name" required="true" type="string">
   <cfargument name="meat" required="true" type="string">
   <cfargument name="originalDate" required="true" type="date">
   <cfargument name="author" required="true" type="string">
   
   <cfset var local = structNew()>
   <cftransaction>
   <cfquery name="local.ins" datasource="#variables._datasource#">
      insert into post
      (name, meat, originalDate, lastModifiedDate, author)
      values
      (<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.name#">,
       <cfqueryparam cfsqltype="cf_sql_longvarchar" value="#arguments.meat#">,
       <cfqueryparam cfsqltype="cf_sql_timestamp" value="#arguments.originalDate#">,
       <cfqueryparam cfsqltype="cf_sql_timestamp" value="#arguments.originalDate#">,
       <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.author#">,
   </cfquery>

   <cfquery name="local.result" datasource="#_datasource#">
      select max(id) as newID from post
      where originalDate=<cfqueryparam cfsqltype="cf_sql_timestamp" value="#arguments.originalDate#">
      and name=<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.name#">
   </cfquery>
   </cftransaction>
   <cfif local.result.recordcount is 0>
      <cfthrow message="The new post was not properly inserted.">
   </cfif>
   <cfreturn local.result.newID>
   </cffunction>

There isn't really anything special here, unless you are new to Coldfusion. If that's the case, you'll want to take note of the <cfqueryparam> tag - using it is considered a "best practice" by most (if not all) experienced Coldfusion developers.

The other item of note is that if you were to run this code by itself, it still wouldn't work, since we haven't defined variables._datasource. Many developers would do this in a function called init() that they call each time they create an object. I've done it as well.

I suppose if you were rigorously following the YAGNI principle, you might wait until creating the next method that would use that variable before defining it. I certainly like YAGNI, but my OCD is not so bad that I won't occasionally allow my ESP to tell me that I'm going to use something, even if I don't yet need it. With that said, I try only do it in the most obvious of cases, such as this one.

Now that we've written the code for insertPost(), its time to run the test again. Doing so, I see that I have two test that run green (this one, and our test_hookup() from earlier. We've gone red-green, so now it's time to refactor. Unfortunately, I don't see any places to do that yet, but I think they'll reveal themselves next time when we write our second test and second method in PostEntity. (To be continued...)


So we decided that blog software centers around posts and that for any other feature to be useful, we'd need them first. Therefore, we'll start with a model component for our posts, and we'll call it PostEntity. Before I create that file though, I'm going to go back into my test_PostEntity.cfc file and write a test or two for some functionality that PostEntity should provide.

Thinking of things we should be able to do regarding the storage of posts, it's easy to identify at least insert(), update(), and delete(). However, since you can't update or delete a post that doesn't exist, I figured I'd start with adding a post. I came up with the following test:

<cffunction name="test_insertPost" access="public" returntype="void" output="false">
      <cfset var local = structNew()>
      <cfset local.nameOfPost = "My Test Post" & left(createUUID(),8)>
      <cfset local.meatOfPost = "The meat of the post is that this is a test." & left(createUUID(),8)>
      <cfset local.dateOfPost = now()>
      <cfset local.author = "Sam #createUUID()#">

      <cfset local.newID=_thePostEntity.insertPost(name=local.nameOfPost, meat=local.meatOfPost, originalDate=local.dateOfPost, lastModifiedDate=local.dateOfPost, author=local.author)>

      <cfquery name="local.post" datasource="#variables._datasource#">
         select name, meat, originalDate, author
         from post
         where id = <cfqueryparam cfsqltype="cf_sql_integer" value="#local.newID#">
      </cfquery>

      <cfset assertEquals(actual=local.post.name, expected=local.nameOfPost)>
      <cfset assertEquals(actual=local.post.meat, expected=local.meatOfPost)>
      <cfset assertEquals(actual=local.post.author, expected=local.author)>

      <!--- dateCompare isn't working correctly, so we are testing each datepart --->      
      <cfset assertEquals(actual=month(local.post.originalDate), expected=month(local.dateOfPost))>
      <cfset assertEquals(actual=day(local.post.originalDate), expected=day(local.dateOfPost))>
      <cfset assertEquals(actual=year(local.post.originalDate), expected=year(local.dateOfPost))>
      <cfset assertEquals(actual=hour(local.post.originalDate), expected=hour(local.dateOfPost))>
      <cfset assertEquals(actual=minute(local.post.originalDate), expected=minute(local.dateOfPost))>
      <cfset assertEquals(actual=second(local.post.originalDate), expected=second(local.dateOfPost))>

      <!--- clean up --->
      <cfquery datasource="#_datasource#">
         delete from post where id = #local.newID#
      </cfquery>
   </cffunction>

You'll notice I used a UUID as part of the data. There's no real point to it, I suppose. I just wanted to have different data each time, and thought this would be a good way to achieve that.

You should also be uncomfortable about the comment saying dateCompare isn't working - I am anyway. It doesn't always fail, but occasionally it does, and for reasons I can't figure out, CFUnit isn't reporting why. For now, so I can move on, I'm assuming it is a bug in CFUnit. Since I can test each date part that is important to me individually and be sure the dates are the same if they all match, I don't feel too bad.

Another thing to note is the use of the var local. By default, any variables created are available everywhere, so to keep them local to a function, you need to use the var keyword. I like to just create a struct called local and put all the local variables in there - it just makes things easier.

Finally, some people might not like the length of that test. Right now, I don't either, but we'll see what we can do about that later. Others may also object to using more than one assertion per test. I don't mind it so much in this case since we really are only testing one thing. If you like, you could also create a struct out of each and write a UDF like structCompare() and do the assertion that way. I haven't tested this one personally, but there is one available at cflib. In either case, I don't see much difference, other than one way I have to write more code than I need.

Now I run the test file we created and find that, as expected, the test still fails. Besides the fact that we don't even have a PostEntity.cfc, we haven't yet instantiated an object of that type, nor have we defined _datasource and the like. Let's do that in the setUp() method.

<cffunction name="setUp" access="public" returntype="void" output="false">
   <cfset variables._datasource="xorblog">
   <cfset variables.pathToXorblog = "domains.xorblog">
   <cfset variables._thePostEntity = createObject("component", "#variables.pathToXorblog#cfcs.src.PostEntity").init(datasource=_datasource)>
</cffunction>

Now our tests still fail, because we have no code or database. So create the datasource and database with columns as needed:

id (int, primary key, autonumber)
name (nvarchar 50)
meat (ntext)
originalDate (datetime)
lastModifiedDate (datetime)
author (nvarchar 50)

Next time, we'll start coding and get our first green test. (To be continued...)


Since I wanted to start this blog, I thought it would be good practice to write the software that runs it using test-driven development. I've used a bit of TDD recently for additions to existing applications, but I've not yet started writing an application using it from beginning to end. I'm getting sick of eating Italian microwaveable dinners when I have to maintain code. This is my chance to eat something else. So, without further ado, we'll jump right in.

The first thing I did of course, was to create my directory structure. For the time being, we have:

xorblog/cfcs/src

and

xorblog/cfcs/tests

I like to keep the tests separate from the source. I don't have a reason behind it, other than it helps keep me a bit organized.

Next, I thought about what a blog needs. We want to deliver items that have the highest business value first, and move on to things that are lower on the value scale later. In doing this, we get a working application sooner rather than later, and hence the blog can be used at the earliest possible moment in its development.

With that in mind, we probably shouldn't start with things like Comments or functionality that lets us get included in places like Technorati. Since you need content to make anything else useful, I thought I'd start with that. Indeed, the Post is the core part of a blog. Therefore, the first thing I did was create test_PostEntity.cfc under xorblog/cfcs/tests.

Now, I'm using CFUnit for my tests, and this assumes you already have it set up. If you need help on that, you can visit CFUnit on SourceForge.

The first thing I do in test_PostEntity.cfc is write test_hookup(), to make sure everything is working:

<cfcomponent extends="net.sourceforge.cfunit.framework.TestCase" output="false" name="test_PostEntity">
   <cffunction name="test_hookup" access="public" returntype="void" output="false">
      <cfset assertEquals(expected=4, actual=2+2)>
   </cffunction>
</cfcomponent>

Next, we need a way to see the status of and run our tests. For this we have test_runner.cfm, which for the most part just copies what you'll find at the CFUnit site linked above:

<cfset testClasses = ArrayNew(1)>
<cfset ArrayAppend(testClasses, "domains.xorblog.cfcs.tests.test_PostEntity")>
<!--- Add as many test classes as you would like to the array --->
<cfset suite = CreateObject("component", "globalcomponents.net.sourceforge.cfunit.framework.TestSuite").init( testClasses )>
<cfoutput>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
   <title>Unit Tests for xorBlog</title>
</head>
<body>
<h1>xorBlog Unit Tests</h1>
<cfscript>
   createobject("component", "globalcomponents.net.sourceforge.cfunit.framework.TestRunner").run(suite,'');
</cfscript>
</body>
</html>
</cfoutput>


Finally, we run that page in a browser to make sure the test runs green - and it does. Now that we have our test environment set up, we can start writing tests for our PostEntity that doesn't yet exist. (To be continued...)



Google
Web CodeOdor.com

Me
Picture of me

Topics
.NET (19)
AI/Machine Learning (14)
Answers To 100 Interview Questions (10)
Bioinformatics (2)
Business (1)
C and C++ (6)
cfrails (22)
ColdFusion (78)
Customer Relations (15)
Databases (3)
DRY (18)
DSLs (11)
Future Tech (5)
Games (5)
Groovy/Grails (8)
Hardware (1)
IDEs (9)
Java (38)
JavaScript (4)
Linux (2)
Lisp (1)
Mac OS (4)
Management (15)
MediaServerX (1)
Miscellany (75)
OOAD (37)
Productivity (11)
Programming (168)
Programming Quotables (9)
Rails (31)
Ruby (67)
Save Your Job (58)
scriptaGulous (4)
Software Development Process (23)
TDD (41)
TDDing xorblog (6)
Tools (5)
Web Development (7)
Windows (1)
With (1)
YAGNI (10)

Resources
Agile Manifesto & Principles
Principles Of OOD
ColdFusion
CFUnit
Ruby
Ruby on Rails
JUnit



RSS 2.0: Full Post | Short Blurb
Subscribe by email:

Delivered by FeedBurner