June 2004 - Posts

Mono 1.0 has me thinking about a Mac

I've always been a PC kind of guy.  Although I learned to program using a Mac back in the day I haven't used one in many years.  When Mac OS X rolled around I got a little more interested, Yao Ming and mini me added a little (ok, not really), and now the release of Mono 1.0 has me really interested.  I'd really like to play with Mono and doing so on one of my three PC's just doesn't seem like it would be right.  I've thought about building a Linux box and throwing it on there but that would mean giving up the ability to code some web services from my front porch...which certainly can't be discounted.  I am extremely exciting about the release of Mono 1.0, I'm sure with .NET 2.0 Beta 1 I'll have a bunch of features I'll want the Mono team to add but with the pace they've been on lately it should only be a couple weeks before we have everything in 2.0 ;-) 

Am I nuts to be thinking about getting a Mac....yeah I think so too!

Breaking news, Microsoft is releasing a set of Express tools!?!?!

Is anyone else sick of hearing about all these Express tools yet, just get me the full beta 1 download please!

Beta 1, can I get a Go Live with that?

We finally have the much anticipated Beta 1 bits of Visual Studio 2005.  The release is exciting as it allows me to better justify the time I spend playing with the bits.  The concerning thing is that I will be extremely tempted to start developing projects using these bits, and since we don't have a Go Live license, well we're not really allowed.  As I mentioned in previous posts I do a fair number of "side projects."  Many of these projects are for smaller companies with ineffiencies in their processes or related busienss problems that a couple small applications could help improve.  The nature of the clients is such that I could (if I was allowed) use beta bits for the projects.  Needless to say I'm looking forward to the next beta and hopefully the Go Live license that will come with it.

Thinking about building a blog app, but aren't there too many?

Over the last couple years I've built a lot of .NET applications. Unfortunetly about 99.9% of them have been web based applications. This has prevented me from getting much experience building WinForms apps. I've written a fair number of little utility apps but there is a lot that I haven't been forced into learning with these little apps. I've recently been working on a lightweight Content Management System that could benefit from a nice WinForms interface. As I've thought about the WinForms CMS App, I've realized that its basically a Blog Client with a couple advanced features. Since there are a couple of pretty good Blog clients already on the seen, and more on the way I kinda feel like I'd be wasting my time re-inventing the wheel. The only other option would be to think up some other WinForm app that makes sense from a "don't waste your time on frivolous projects" point of view.

MIT Open Courseware

Just a reminder to myself to check out MIT's Open Course Ware. Continue on...

Looking forward to the Beta

I like to keep up to date on the latest and greatest coming out of Microsoft.  A while back I had the opportunity to play with some Alpha releases of Whidbey.  Although it was fun to get an early look at some of the stuff coming out in the next release I felt that from now on I would wait until at least Beta 1.  This probably puts me a little further back in regards to getting a hand on feel for all the cool stuff coming, but I don't have much other choice.  I spend a lot of my “free” time doing side projects for all sorts of different clients.  Most of the projects are pretty small but they do allow me to bring in a little bit of extra cash flow which never hurts.  Since I certainly can't use an Alpha for these side projects it's extremely difficult for me to spend much time mucking around with all the new features.  Needless to say I'm looking forward to a Beta that has a Go Live license so I can start using Generics and all the other cool features in Whidbey.  Since Beta 1 is closer to that all important go live license I might be able to justify a little time playing.  Either that or I need to figure out how to get on the team Craig is on for the MSDN “project.” 

What was the best job you ever had?

I was recently talking to a couple people about the best job (or project) they ever worked on.  It was interesting to hear their responses as well as the things that made the projects/jobs their “coolest.“ Whats the best project or job you've ever had?  Why?

A code addiction

One of the things I've been realizing lately is that I have a bit of an addiction to code.  Whenever I find a new source code release that seems interesting I can't help but dig into it.  Although it can sometimes be hard to get a whole heck of a lot our of just a set of source files I find myself endlessly wondering around within the source tree.  Evaluating how things are written, what patterns are being used, comparing it to code I've written, and considering alternate design choices that could have made things cleaner or more “elegant.”

Clemens is breaking down FABRIQ

Clemens has started to break down the elements that make up FABRIQ.  If you interested in SOA and a real world implementation I'd recommend subscribing to Clemens feed to get the latest scoop.  It's interesting to see the similarities and differences between FABRIQ and Microsoft's Enterprise Development Reference Architecture (EDRA) aka Shadowfax.  I'll definitely be looking forward to the code drop of FABRIQ!

What was I thinking with that refactoring?

Tonight as I was working on some enhancements to the base framework that I'm using on a couple projects I noticed that the design was kinda...well...terrible.  Rather then take a nice phased approach to refactoring the particular classes that were rather messy I did a mass refactoring.  About  half way through it I stopped and wondered....“what in the world am I doing?”  Since I was so far into it I decided to try and complete the refactoring, if I had failed I would have completely backed out and started over using a phased approach.  After a couple red bars, and a couple additional refactorings I was seeing all GREEN!  Wow, that was one of the less intelligent mass refactorings I'd ever tried, thank goodness I had a full suite of unit tests to validate that I didn't screw something up along the way.

Anyone have a "Requirements documentation" tool?

We're currently looking for a requirements documentation tool which can replace the current system we developed in house.  We need a tool which:
  • Is web-based
  • Allows an admin to create an arbitrary number of attributes assignable to each requirement, and an arbitrary number of values for each attribute
  • Allows requirements to be linked, referenced, sorted and extracted through various reports
  • Has the ability to 'escalate' specific requirements or questions into a 'client-viewable' state, such that the client can review, modify, and approve those requirements; however the rest of the discussion around the requirement should remain visible only to internal  users
 
In addition, our current tool has a Use Case Modeler which:
  •  References requirements/issues in use cases, allowing the reviewer to see the context of a use case
  •  Tracks preconditions and postconditions, as references other use cases
  •  Tracks actors, system components and other attributes of a system, to allow for filtered viewing of relevant use cases.

Anyone have any recommendations?

Automating Unit Testing With a Base Class Posts

I've recently written a series of posts on the process of automating the unit testing of CRUD operations on business objects. 

In future posts I'll dive into some of the details which I didn't go into such as how to set the allowable values for a property, how to ignore a property when comparing objects, how to set a property value as unique, as well as how to manage relationships among objects.  Look for a zip file containing a running example in the next couple of days.

Part 3: Automating Unit Testing with a Base Class

In Part 1 of Automating Unit Testing with a Base Class I provided a brief introduction to Unit Testing, provided an overview of the problems that unit testing business objects present, and briefly discussed why I include the database in my unit tests.  In part 2, I provided an overview of the process I've followed in testing the CRUD operations of my business objects.  In this final installment I'm going to discuss how I've simplified the testing of basic CRUD operations on my business objects by creating a base class for my business object unit tests.

Let's Review

Before moving on to the solution I'd like to quickly review the process I follow in testing the CRUD operations on my business objects.

Create

  •  Instantiate an instance of the object
  • Set the properties of the object with valid values.
  • Call the Save() method on the object, checking that the save succeeds.

Read

  • Do Create.
  • Retrieve the object out of the data store and check that the values of the saved object equal the values pulled from the data store.

Update

  • Do Read
  • Change the properties of the object.
  • Call the Save() method on the object.
  • Retrieve the object out of the data store and check that the values of the updated object equal the values pulled from the data store.

Delete

  • Do Create
  • Call Delete() on the object.
  • Try reading the object back out of the data store and ensure we get a null object (since its deleted).

See Part 2 for the code representation of the above

What can we Automate?

Over the past year I've written a lot of unit tests for the CRUD operations on my business objects.  A couple months ago I was working on a moderately sized .NET project coding away, writing failing tests for each of my CRUD operations, writing the code to make the tests past, and refactoring my way to cleaner code.  The process was feeling pretty good, except for one thing.  I seemed to be writing the same code over and over.  Duplication is bad, so why do I allow myself to write 50 test classes with almost identical logic for testing the CRUD operations on my objects?  I try to follow the Don't Repeat Yourself principle that Dave Thomas and Andy Hunt present in The Pragmatic Programmer so I set out to find a better way.

As I began my journey I began to evaluate the tests that I was writing for my business objects.  The tests were slightly different across the project but I saw a similarity among them that led me down an interesting path.  Each test object was essentially performing 3 different tasks, the only difference between the tests was the objects that they were performing the tasks on.

  • Task 1 - Loading objects with data.
  • Task 2 - Calling methods on my business objects (Save, Delete, Load).
  • Task 3 - Comparing two business objects to ensure the values saved and the values loaded were identical.

Task 1 - Loading Objects with data

The first task I identified was the process of loading objects with data.  In order to test the CRUD operations on my objects I first needed to load the objects with data so they could be saved to the database.  Let's take a quick look at how the data was loaded in the unit tests outlined in Part 2 of this series:

Customer customer = new Customer();
customer.ContactName = "Steve Eichert";
customer.Address = "221 South North West Ave.";
customer.City = "Philadelphia";
customer.Region = "PA";

To load the Customer object with data I instantiate an instance of the Customer class and then set the properties of the customer.  The class being instantiated and the properties being set change for each unit test, however, the process is the same.  First instantiate an instance of the class and then set the properties of that class.  Now that the task has been identified how can it be automated?

Provided we have the type of the business object we can use Activator.CreateInstance to instantiate an instance of the class.

BusinessObject bizObject = (BusinessObject) Activator.CreateInstance(BusinessObjectType);

After an instance of the class is created we need a method for setting the properties of the object.  The System.Reflection namespace gives us just what we need.  By using the GetProperties() method on the System.Type object we can identify all the properties defined on our objects.

///

<summary>
/// Load the properties of an BusinessObject with random values.
/// </summary>
/// <param name="bizObject">The <c>BusinessObject</c> to load the properties of.</param>
public
void
LoadProperties(BusinessObject bizObject) {
   System.Reflection.PropertyInfo[] properties = BusinessObjectType.GetProperties();
    foreach(PropertyInfo property in properties) {
      if(property.CanWrite) {
      
SetDynamicPropertyValue(bizObject, property);
     }
   }
}

The LoadProperties() method retrieves all the properties available on our business object and then loops over all the properties and sets appropriate values (SetDynamicPropertyValue).  Depending on the type (string, int, bool, etc) of the property we need to set a different value.  We also need to consider that certain properties have a limited set of potential values.  To set the value of each property we need to first determine the type of property using the PropertyType property of the PropertyInfo object (say that 10 times fast).  The below block of code shows how this can be accomplished.

///

<summary>
/// Set the property of an <c>BusinessObject</c> to a random (dynamic) value.
/// </summary>
/// <param name="bizObject">The <c>BusinessObject</c> to set the value of.</param>
/// <param name="property">The <c>PropertyInfo</c> to set the value of.</param>
private void SetDynamicPropertyValue(BusinessObject bizObject, PropertyInfo property) {
  Random random;
  string propertyKey = bizObject.GetType().Name + "-" + property.Name;
  if(_allowableValues[propertyKey] != null) {
    
object[] values = (object[]) _allowableValues[propertyKey];
    random =
new Random(Environment.TickCount);
    property.SetValue(bizObject, values.GetValue(random.Next(values.Length)),
null);
  }
else {
    ArrayList usedValues = (ArrayList)_uniqueValues[propertyKey];
    
switch(property.PropertyType.ToString()) {
      
case "System.String":
          property.SetValue(bizObject, GetRandomString(usedValues),
null);
         
break;}
      
case "System.Int32":
      
case "System.Double":
          random =
new Random(Environment.TickCount);
          property.SetValue(bizObject, random.Next(1, 9999),
null);
          
break;
       
case "System.Decimal":
          random =
new Random(Environment.TickCount);
          property.SetValue(bizObject, Convert.ToDecimal(random.Next(1, 9999)),
null);
         
break;
      
case "System.DateTime":
          Random dayRandom =
new Random(Environment.TickCount);
          Random monthRandom =
new Random(Environment.TickCount);
          property.SetValue(bizObject, DateTime.Parse(monthRandom.Next(1, 12) + "/" +
                                                                          dayRandom.Next(1, 2 + "/" +
                                                                          DateTime.Now.Year),
null);
         
break;
      
case "System.Boolean":
          random =
new Random(Environment.TickCount);
          property.SetValue(bizObject, Convert.ToBoolean(random.Next(0, 1)),
null);
         
break;
      
default:
         
if(property.PropertyType.BaseType != null && property.PropertyType.BaseType.ToString() == "System.Enum") {
               Array values = Enum.GetValues(property.PropertyType);
               random =
new Random(Environment.TickCount);
               property.SetValue(bizObject, values.GetValue(random.Next(values.Length)),
null);
          }
         
break;
       }
   }
}

The SetDynamicPropertyValue() method accepts the business object being tested (bizObject) as well as the current property that we need to assign a value to (property).  The method evaluates the type of property using the "PropertyType" property of the PropertyInfo object.  There's a couple utility methods that I'm not going to discuss at the moment as to stay on topic.  The bottom line is that by inspecting the PropertyType attribute of each property that we retrieve from the business object we can determine a valid value to assign to the property.  Once we find a value we can use the SetValue method on the PropertyInfo object to assign the property to our business object.

property.SetValue(bizObject, "A property value.", null);

Now that we've automated the process of loading our business  objects with values, lets move on to task 2, calling methods on our object.

Task 2 - Calling Methods

Now that we've instantiated our business object using Activator.CreateInstance, and set the all the properties of our objects using LoadProperties() and SetDynamicPropertyValue(), we need to call the methods on our object and ensure they provide the proper result.  Since all of our business objects inherit from a base BusinessObject class this is extremely easy to automate.  Let's first look back at the interface for our business object base class:

public abstract class BusinessObject {
   public BusinessObject() {}
   abstract public int ID { get; set; }  
   abstract
public void Load(string key);
   abstract public bool Save();
   abstract public bool Delete();
}

Since we already have an instance of a BusinessObject class we can call Save, Load, and Delete.  The base class provides a common interface that allows us to write code in our base test class in a generic fashion.  Rather then instantiating particular types of business objects we simply create an instance of our class using Activator.CreateInstance and then call the necessary methods.

protected
abstract Type BusinessObjectType { get; } 

/// <summary>
/// Test that the business object can be saved.
/// </summary>
[Test]
public virtual void CanBeSaved() {
   Save(BusinessObjectType);
}

/// <summary>
/// Test if the BusinessObject can be deleted.
/// </summary>
[Test]
public
virtual void CanBeDeleted() {
   BusinessObject bizObject = Save(BusinessObjectType);
   Assert.IsTrue(bizObject .Delete(), "The object could not be deleted.");
}


///
<summary>
/// Helper method for saving and business object.
/// </summary>
/// <param name="bizObjectType">The type of business object to save.</param>
protected BusinessObject Save(Type bizObjectType) {
   BusinessObject bizObject = (BusinessObject) Activator.CreateInstance(BusinessObjectType);
   LoadProperties(bizObject);
   SaveAsIs(bizObject);
    return bizObject;
}


///
<summary>
/// Helper method for saving and business object in its current state.
/// </summary>
/// <param name="bizObject">The <c>BusinessObject</c> to save.</param>
private
BusinessObject SaveAsIs(BusinessObject bizObject) {
   Assert.IsTrue(bizObject.Save(), "The " + bizObject.GetType().Name + " could not be saved.");
    return bizObject;
}

Task 3 - Comparing Objects

Now that we've successfully figured out how to load data into our objects as well as call the necessary methods on our objects we're only one step away from having all the pieces in place for the automation of our CRUD unit tests.  The final piece of the puzzle is the comparison of objects.  In order for us to ensure the properties assigned to our objects are persisted to the database properly we need to have the ability to compare two different objects.  In part 2, this was accomplished using the following code.

public void CanBeRead() {
   Customer savedCustomer =
new Customer();
   // ...set properties
   Assert.IsTrue(savedCustomer.Save());
   // read the customer
   Customer readCustomer = new Customer();
   readCustomer.Load(savedCustomer.CustomerID);
   // check properties of the loaded object against the saved object
   Assert.AreEqual(savedCustomer.ContactName, readCustomer.ContactName, "ContactName properties are not equal.");
   Assert.AreEqual(savedCustomer.ContactTitle, readCustomer.ContactTitle, "ContactTitle properties are not equal.");
   Assert.AreEqual(savedCustomer.Address, readCustomer.Address, "Address properties are not equal.");
   // check remaining properties as necessary...
}

As you can see we're manually comparing each property of the savedCustomer object to the same property on the readCustomer object.  We can again use Reflection to retrieve the properties of the business object class to help automate the process of comparing objects.  By looping over each property and comparing the values retrieved from the business objects we can determine if two business objects are "equal."

/// <summary>
/// Test if the business object can be successfully loaded by first saving
/// and then loading and comparing each property value.
/// </summary>
[Test]
public

virtual void CanBeLoaded() {
   BusinessObject savedBizObject = Save(BusinessObjectType);
   BusinessObject bizObject = ((BusinessObject) Activator.CreateInstance(BusinessObjectType);
   bizObject.Load(savedBizObject.ID);

   CompareBusinessObjects(savedBizObject, bizObject);
}

public void CompareBusinessObjects(BusinessObject bizObject, BusinessObject comparedBizObject) {
   // Get all properties and loop through them to ensure the values of the saved
   // business object are the same as the freshly loaded object.
   System.Reflection.PropertyInfo[] properties = BusinessObjectType.GetProperties();
   foreach(PropertyInfo property in properties) {
       if(property.CanWrite && !_ignoredProperties.Contains(bizObjectType.Name + "-" + property.Name))
         Assert.IsTrue(property.GetValue(bizObject,
null).Equals(property.GetValue(comparedBizObject, null)), property.Name + " of the loaded
                                               object is not equal to the value of the saved object.");
     }
   }

}

Wrapping Up

By focusing on the automation of the three common tasks that I was performing in all of my business object unit tests I was able to develop a base unit test that handled the testing of the major CRUD operations on my objects.  By automating this process I am able to focus my attention on the areas that deserve the most attention, the domain layer.  Rather then writing a bunch of very similar tests for testing the CRUD operations on my business objects I'm able to write a bunch of very different tests for the domain layer within my application.  This results in better productivity, and better quality software.

Conclusion

The process of unit testing CRUD operations on business objects can be a very tedious task.  By leveraging a base unit test class we are able to automate this process which greatly reduces the amount of time required to get a suite of unit tests up and running for our objects. 

Although there are a lot of details that I haven't covered in this series of posts on Automating Unit Tests with a Base Class I hope you have seen the advantages that can be gained.  In future posts I'll dive into more of the details and gotcha's that I didn't cover in this first series of articles. 

Did anyone actually make it all the way down  here?   If so please post your thoughts and comments!!

 

Simplified Unit Testing with a Database

Roy had a bit of an epiphany while reading TDD in .NET.  You can find the outcome in his most excellent article on using Enterprise Services to simplify Test Driven Development (TDD) with a database.

What do you do?

Why is it that I dread that question so much?  Whenever someone asks, “so, what do you do?” I pause for a second thinking about what the chances are that the person I'm talking to might actually have enough technical background to have any clue what I'm about to say.  I usually conclude that there is about 0% chance that they will have even the slightest clue.  Could it be that the way I go about explaining what I do is just incomprehensible to all but those in the same profession?  When someone asks what you do, what do you say?  Do you tailor it to the audience or just give them the description you've come up with and hope for the best?  Share your “what do you do“ description in the comments of this post so I can give it a try on the next sorry soul who asks me. :-)

 

Unit Testing in all versions of VS 2005, who could disagree?

Unit Testing support should be included with all versions of Visual Studio 2005 and not just with Team System. [Peter Provost]

Who could disagree with that statement?  Unit testing and TDD should be tools that every developer is using no matter what version of VS.NET their using.  Help bring TDD to the masses, include Unit Testing in even the most basic versions of VS.NET!  Oh, and if you agree make sure you comment/trackback Peter's original post.

 

Is architecture worth the investment?

[Note: I wrote this post once already in the web interface, which when the site went down was lost.  As you all know the second time around writing something is never as good]

I've recently been advocating the creation of a architecture group or role within our technology department.  Over the last year we've developed several frameworks and underlying architectures that we use on almost every project we do.  The frameworks have saved us a considerable amount of time and have enabled us to deliver more functionality and better quality software then had we not used them. 

I believe a good architecture and framework can be extremely beneficial.  They offer many advantages such as:

  • Increased productivity for developers.
  • Increased quality of the solutions.
  • Standard design across projects.

The problem with having a person or team dedicated to the underlying frameworks and architectures that we use on our projects stem from the fact that we're a services company.  Every hour that an individual works on the framework is an hour that could have been spent billing the client.  We currently don't tack on a line item within our estimates for the underlying frameworks which we've invested in, so essentially the time spent working on the frameworks (unrelated to a client project) are "investments" that we don't see any return (in the $$ sense) from.

Do you currently have a team or individual who is responsible for the underlying framework and architectures used in building solutions for your clients?  If so, what benefits do you see coming from this?  What value is provided by the frameworks?  How is the investment justified?

 

 

Project Failure Factors

How many of these were on your last project?

  • Lack of User Input
  • Incomplete Requirements & Specifications
  • Changing Requirements & Specifications
  • Lack of Executive Support
  • Technology Incompetence
  • Lack of Resources
  • Unrealistic Expectations
  • Unclear Objectives
  • Unrealistic Time Frames
  • New Technology

Dale Emery offers the The Prime Project Failure Factor which will prevent a project from failing even if all of the above factors appear in the project.

A Fresh Look at the Waterfall

Did you ever think that the stages defined within a waterfall process were a little inaccurate.  David Pinn decided to rename the phases according to what really happens.

Analysis

Dream

Design

Guess and Waffle

Build

Hack and Play

Test

Wobble and Groan

Deploy

Push and Pray

Support

Duck and Deny










http://byandlarge.net/thebitterend/archives/000210.html

 

Ask Craig

Craig Andera posted an email thread which offers some good insight and advice.

See, here's the thing that no one tells you: learning to be a good computer
programmer takes at least ten years. Which means that just about no one
coming out of college is any good. The smart employers (and believe me,
there are a lot of dumb ones) know this, and aren't really looking for
people that already have really strong programming skills. They're looking
for people with strong *thinking* skills, since those people can be taught
to do just about anything well.

...

My parting advice to you is that, regardless of what major you choose, be
sure to keep programming. Remember - it takes at least ten years to get
good. It sounds like you've already got a great start. (And you write well,
too, which is also a really good sign.) But make sure you take what you've
done already and keep building on it. Write code. Study design. Write more
code. Even familiarize yourself with some of the non-technology aspects of
programming like how team-based development works (e.g. pair programming,
test-driven development, etc.) You're going to need to do all of this - a
lot - before you can rightfully consider yourself an elite programmer. I
know I'm still learning.

TDD w/ .NET Webcast

Check out the Test Driven Development with .NET Webcast (Thanks for the reminder Roy!)

Developers as Athletes

A couple weeks ago a couple peeps in the blog world compared software developers to athletes.  As a former collegiate athlete I agree that the analogy is a good one...

Great developers and great athletes strive to be the best, and have the talent and drive to become so. The best developers are the ones who seek out help when they need it, and take steps to actively seek out learning opportunities on their own.

What I have a problem with is the fact that I feel nothing like an athlete because of trying to become a great developer.  Over the past couple months I've been feeling a sense of overall crapiness.  Weird chest thingies, strange heart happenings, and an overall sh**y feeling in general.  My self diagnosis is that I'm too friggin out of shape and need to start working out again.  To that end I decided to go for a nice little jog this afternoon.......OUCH!  I'm so ridiculously out of shape it's absurd.  So in summary....

Striving to become a great developer may prevent you from feeling like any kind of athlete, let alone a great one!

Domain Driven Design rocks!

I'm only about half way through Domain Driven Design by Eric Evans but I'm thoroughly enjoying what I've covered so far.  I think Matin's PoEEA and Evans DDD should be required reading for all!  Look for a more detailed review in a couple weeks when I finish it off...

Domain Driven Design rocks!

I'm only about half way through Domain Driven Design by Eric Evans but I'm thoroughly enjoying what I've covered so far.  I think Matin's PoEEA and Evans DDD should be required reading for all!  Look for a more detailed review in a couple weeks when I finish it off...

Part 2 Follow Up: But those tests suck!?!

In Part 2 of Automating Unit Tests with a Base Class I provided a set of example tests for CRUD operations.  Although the logic is correct the implementation...kinda....well....sucks!  The main problem is that many of the tests are dependent on functionality that is not directly being tested within the test method.  As an example let's look at the CanBeRead() test...

1 public void CanBeRead() {
2
3   Customer savedCustomer = new Customer();
4   // ...set properties
5
6   Assert.IsTrue(savedCustomer.Save());
7
8   // read the customer
9   Customer readCustomer = new Customer();
10   readCustomer.Load(savedCustomer.CustomerID);
11
12   // check properties of the loaded object against the saved object
13   Assert.AreEqual(savedCustomer.ContactName, readCustomer.ContactName, "ContactName properties are not equal.");
14   Assert.AreEqual(savedCustomer.ContactTitle, readCustomer.ContactTitle, "ContactTitle properties are not equal.");
15   Assert.AreEqual(savedCustomer.Address, readCustomer.Address, "Address properties are not equal.");
16
17   // check remaining properties as necessary...
18 }

In order to test the “read“ of the object the test first calls the .Save() method on the customer object.  If the .Save() method fails we get a failure for that test and will quickly assume our read logic is incorrect.  Unfortunately by including the .Save() within the read test we get an inaccurate view into what is causing problems within our application.  A better approach is to manually insert the records  using a SQL statement.  This prevents a failure in the .Save() from crashing our read test and ensures our tests only fail for a single reason.

So why did I present the test this way?  Believe it or not I'm actually using code very similar to this in my test projects.  Rather then calling INSERT's to place my test data in my database I'm using the .Save() method on my objects.  This allows me to quickly test my objects and also makes the base unit test which I'm sure you are all anxiously awaiting easier to implement.  Perhaps after I present my solution to automating the unit tests for CRUD operations I can clean the code up and implement a base test class that uses SQL statement to perform the necessary population and removal of test data. 

Have I lost all my TDD/Unit Testing “street cred” by coming out of the closet and admitting to this heinous crime? :-(

Part 2 Follow Up: But those tests suck!?!

In Part 2 of Automating Unit Tests with a Base Class I provided a set of example tests for CRUD operations.  Although the logic is correct the implementation...kinda....well....sucks!  The main problem is that many of the tests are dependent on functionality that is not directly being tested within the test method.  As an example let's look at the CanBeRead() test...

1 public void CanBeRead() {
2
3   Customer savedCustomer = new Customer();
4   // ...set properties
5
6   Assert.IsTrue(savedCustomer.Save());
7
8   // read the customer
9   Customer readCustomer = new Customer();
10   readCustomer.Load(savedCustomer.CustomerID);
11
12   // check properties of the loaded object against the saved object
13   Assert.AreEqual(savedCustomer.ContactName, readCustomer.ContactName, "ContactName properties are not equal.");
14   Assert.AreEqual(savedCustomer.ContactTitle, readCustomer.ContactTitle, "ContactTitle properties are not equal.");
15   Assert.AreEqual(savedCustomer.Address, readCustomer.Address, "Address properties are not equal.");
16
17   // check remaining properties as necessary...
18 }

In order to test the “read“ of the object the test first calls the .Save() method on the customer object.  If the .Save() method fails we get a failure for that test and will quickly assume our read logic is incorrect.  Unfortunately by including the .Save() within the read test we get an inaccurate view into what is causing problems within our application.  A better approach is to manually insert the records  using a SQL statement.  This prevents a failure in the .Save() from crashing our read test and ensures our tests only fail for a single reason.

So why did I present the test this way?  Believe it or not I'm actually using code very similar to this in my test projects.  Rather then calling INSERT's to place my test data in my database I'm using the .Save() method on my objects.  This allows me to quickly test my objects and also makes the base unit test which I'm sure you are all anxiously awaiting easier to implement.  Perhaps after I present my solution to automating the unit tests for CRUD operations I can clean the code up and implement a base test class that uses SQL statement to perform the necessary population and removal of test data. 

Have I lost all my TDD/Unit Testing “street cred” by coming out of the closet and admitting to this heinous crime? :-(

Part 2: Automating Unit Tests with a Base Class

Part 1 of Automating Unit Tests with a Base Class provided a brief summary of the problems we run into while writing unit tests for our business objects, and discussed why it's important that the database be included.  In this post I'm going to describe the process of writing unit tests for business objects.  In part 3 I will detail how we can automate the testing of our CRUD operations via a base unit test class.

Testing Business Objects

CRUD operations are the primary operation we carry out on our business objects.    Each of these CRUD operations involves a set of steps that is performed for each type of object.  To ensure that the operations work for all of our business objects it is important to have unit tests that verify the expected results are provided when we perform each operation.  Before diving into the specifics of how we test our business objects lets first take a look at the interface of our business objects (for the purposes of this article). 

1	public abstract class BusinessObject {
2
3 public BusinessObject() {
4 }
5
6 abstract public void Load(string key);
7 abstract public bool Save();
8 abstract public bool Delete();
9 }

The BusinessObject class is used as the base class for our business objects.  The class provides the common interface for our business objects.  We can see that our business objects support saving themselves to the data store (Create, Update) via the Save() method, deleting themselves from the data store (Delete) via the Delete() method, and finally loading themselves from the data store (Read) via the Load() method.

Now that we have overviewed the base BusinessObject class let's take a look at an example Customer object.

1public class Customer : BusinessObject	{
2
3 public Customer() : base() {}
4
5 public override bool Delete() {
6 // Create DAL, and remove from the database...
7 return true;
8 }
9
10 public override bool Save() {
11 // Create DAL, and save to the database...
12 return true;
13 }
14
15 public override void Load(string customerID) {
16 // Create DAL, and load from the database...
17 }
18
19 public string CustomerID {
20 get { return _customerID; }
21 set { _customerID = value; }
22 }
23
24 public string CompanyName {
25 get { return _companyName; }
26 set { _companyName = value; }
27 }
28
29 public string ContactName {
30 get { return _contactName; }
31 set { _contactName = value; }
32 }
33
34 public string ContactTitle {
35 get { return _contactTitle; }
36 set { _contactTitle = value; }
37 }
38
39 public string Address {
40 get { return _address; }
41 set { _address = value; }
42 }
43
44 public string City {
45 get { return _city; }
46 set { _city = value; }
47 }
48
49 public string Region {
50 get { return _region; }
51 set { _region = value; }
52 }
53
54 public string PostalCode {
55 get { return _postalCode; }
56 set { _postalCode = value; }
57 }
58
59 public string Country {
60 get { return _country; }
61 set { _country = value; }
62 }
63
64 public string Phone {
65 get { return _phone; }
66 set { _phone = value; }
67 }
68
69 public string Fax {
70 get { return _fax; }
71 set { _fax = value; }
72 }
73
74
75 private string _customerID = String.Empty;
76 private string _companyName = String.Empty;
77 private string _contactName = String.Empty;
78 private string _contactTitle = String.Empty;
79 private string _address = String.Empty;
80 private string _city = String.Empty;
81 private string _region = String.Empty;
82 private string _postalCode = String.Empty;
83 private string _country = String.Empty;
84 private string _phone = String.Empty;
85 private string _fax = String.Empty;
86 }

The Customer class provides a set of properties for managing important information about the customer, as well as an implementation for saving, deleting, and loading customers from the data store.  The actual implementation of the save, delete, and load is not the focus of this article, however, the fact that each business object implements the interface for performing these operations via the BusinessObject class is important.  By inheriting from the BusinessObject class our objects are agreeing to implement the interface we've defined.  When we begin looking at how we can automate the unit tests for the CRUD operations on our objects this common interface will prove itself extremely valuable.

Unit Testing CRUD Operations

Now that we've covered the basics of our business objects lets take a quick look at the details surrounding the unit testing of the CRUD operations for our objects.  Below is a list of the four CRUD operations and the process we follow for unit testing each operation.  I've included a sample test for each operation that provides one (of many) ways of implementing the tests.  

Create -

1.      Instantiate an instance of the object.

2.      Set the properties of the object with valid values.

3.      Call the Save() method on the object, checking that the save succeeds.


1 public void CanBeSaved() {
2
3 Customer customer = new Customer();
4 customer.ContactName = "Steve Eichert";
5 customer.Address = "221 South North West Ave.";
6 customer.City = "Philadelphia";
7 customer.Region = "PA";
8 // ...set other properties
9
10 Assert.IsTrue(customer.Save());
11
12 }

Read -

1.      Do Create.

2.      Retrieve the object out of the data store and check that the values of the saved object equal the values pulled from the data store.


1 public void CanBeRead() {
2
3 Customer savedCustomer = new Customer();
4 // ...set properties
5
6 Assert.IsTrue(savedCustomer.Save());
7
8 // read the customer
9 Customer readCustomer = new Customer();
10 readCustomer.Load(savedCustomer.CustomerID);
11
12 // check properties of the loaded object against the saved object
13 Assert.AreEqual(savedCustomer.ContactName, readCustomer.ContactName, "ContactName properties are not equal.");
14 Assert.AreEqual(savedCustomer.ContactTitle, readCustomer.ContactTitle, "ContactTitle properties are not equal.");
15 Assert.AreEqual(savedCustomer.Address, readCustomer.Address, "Address properties are not equal.");
16
17 // check remaining properties as necessary...
18 }

Update

1.      Do Read

2.      Change the properties of the object.

3.      Call the Save() method on the object.

4.      Retrieve the object out of the data store and check that the values of the updated object equal the values pulled from the data store.

1public void CanBeUpdated() {
2
3 // read the customer
4 Customer updateCustomer = new Customer();
5 updateCustomer.Load("CUST-99");
6 updateCustomer.ContactName = "New Contact Name";
7 // ...change other properties as necessary
8
9 Assert.IsTrue(updateCustomer.Save());
10
11 // read the customer
12 Customer readCustomer = new Customer();
13 readCustomer.Load(updateCustomer.CustomerID);
14
15 // check properties of the loaded object against the saved object
16 Assert.AreEqual(updateCustomer.ContactName, readCustomer.ContactName, "ContactName properties are not equal.");
17
18 // check remaining properties as necessary...
19
20}

Delete -

1.      Do Create

2.      Call Delete() on the object.

3.      Try reading the object back out of the data store and ensure we get a null object (since its deleted).

1public void CanBeDeleted() {
2
3 Customer customer = new Customer();
4 // ... set properties
5
6 Assert.IsTrue(customer.Save());
7
8 Assert.IsTrue(customer.Delete());
9
10 // read the customer
11 Customer readCustomer = new Customer();
12 readCustomer.Load(customer.CustomerID);
13
14 // ... check that the readCustomer is not valid through some mechanism
15 Assert.AreEqual("-1", customer.CustomerID, "The deleted object still exists!");
16
17}

To ensure each business objects is successfully handling these four CRUD operations we need to write four (at a minimum) unit tests for each business object in our system.  On a decent sized project this forces us to write a lot of redundant code. 

Rather then writing tests after test for every business object, we can automate the process of testing the basic CRUD operations by creating a base unit test class.  With a base test class in place we can focus on testing the pieces of the application that need it the most rather then the rudimentary CRUD operations.   By incorporating a base test class into our testing framework we can remove the burden of writing tests for each CRUD operations and instead focus on writing tests for the business logic within our application.

Next up:  How do we accomplish our goal of automating our tests?

[Now Playing: The Wallflowers - The Wallflowers - After the Black Bird Sings (04:4]

Three/Multi Tier/Layer Architecture/Design by Scott Hanselman

Listen to him... Thank you.