Posted by Sam on Oct 18, 2006 at 11:12 AM UTC - 6 hrs
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!
Last modified on Oct 18, 2006 at 11:14 AM UTC - 6 hrs
Posted by Sam on Aug 29, 2006 at 09:41 AM UTC - 6 hrs
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.
Last modified on Aug 29, 2006 at 09:51 AM UTC - 6 hrs
Posted by Sam on Aug 29, 2006 at 09:27 AM UTC - 6 hrs
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
. 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:
<cfset var local = structNew()>
<cfset local.wasDeleted = _thePostEntity.deletePost(local.newID)>
<cfset assertTrue(condition=local.wasDeleted, message=
"The post was not deleted.")>
select id from post where id =
<cfset assertEquals(actual = local.post.recordcount, expected = 0)>
And the corresponding code for
<cfset var local = structNew()>
<cfset local.result = false>
delete from post where id =
Originally, I just left the test as asserting that
was true. However, writing just
in xorblog/cfcs/tests/PostEntity to get the test to pass resulted in the
. 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...)
Posted by Sam on Aug 28, 2006 at 11:46 AM UTC - 6 hrs
We left off after writing the test for the
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
What follows is that code:
<cfset var local = structNew()>
insert into post
(name, meat, originalDate, lastModifiedDate, author)
select max(id) as newID from post
<cfif local.result.recordcount is
"The new post was not properly inserted.">
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
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
. Many developers
would do this in a function called
that they call each time they create an object. I've
done it as well.
I suppose if you were rigorously following the
principle, you might wait until creating the next
method that would use that variable before defining it. I certainly like YAGNI, but my
is not so bad that I won't occasionally allow
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
, its time to run the test again. Doing so,
I see that I have two test that run green (this one, and our
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
. (To be continued...)
Last modified on Aug 28, 2006 at 11:52 AM UTC - 6 hrs
Posted by Sam on Aug 25, 2006 at 12:34 PM UTC - 6 hrs
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
. 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
Thinking of things we should be able to do regarding the storage of posts, it's easy to identify
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:
<cfset var local = structNew()>
<cfset local.nameOfPost =
"My Test Post" & left(createUUID(),
<cfset local.meatOfPost =
"The meat of the post is that this is a test." & left(createUUID(),
<cfset local.dateOfPost = now()>
<cfset local.author =
<cfset local.newID=_thePostEntity.insertPost(name=local.nameOfPost, meat=local.meatOfPost, originalDate=local.dateOfPost, lastModifiedDate=local.dateOfPost, author=local.author)>
select name, meat, originalDate, author
where id =
<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 --->
delete from post where id = #local.newID#
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
Another thing to note is the use of the
. By default, any variables created
are available everywhere, so to keep them local to a function, you need to use the
I like to just create a
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
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
and the like. Let's do that in the
<cfset variables.pathToXorblog =
<cfset variables._thePostEntity = createObject(
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)
author (nvarchar 50)
Next time, we'll start coding and get our first green test. (To be continued...)
Last modified on Aug 29, 2006 at 08:38 AM UTC - 6 hrs
Posted by Sam on Aug 20, 2006 at 10:38 AM UTC - 6 hrs
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:
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
that lets us get included in places like Technorati
you need content to make anything else useful, I thought I'd start with that. Indeed, the
the core part of a blog. Therefore, the first thing I did was create test_PostEntity.cfc under
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
to make sure everything is working:
<cfset assertEquals(expected=4, actual=2+2)>
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(
<!--- Add as many test classes as you would like to the array --->
<cfset suite = CreateObject(
"globalcomponents.net.sourceforge.cfunit.framework.TestSuite").init( testClasses )>
<!DOCTYPE HTML PUBLIC
-//W3C//DTD HTML 4.01 Transitional//EN">
<title>Unit Tests for xorBlog
<h1>xorBlog Unit Tests
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
that doesn't yet exist. (To be continued...)
Last modified on Aug 20, 2006 at 10:49 AM UTC - 6 hrs