Monday, April 04, 2005

Unit testing and data access

Something that has come up in conversation recently wrt unit testing is the subject of how to unit test complex code fragments that use data persistence code, without having to interfere with source databases / reset data etc. It would be great to be able to test the code with 'static' data that is known to be correct (or incorrect if testing exceptions) that passes through the DAL, but does not require 'real' data access (e.g. you might want to unit test a business component method that internally calls the GetMyObject() method).

My current DAL technique of choice turns out to have a nice side effect in making this possible. I tend to start development by creating an abstract data layer with no concrete implementation. I then use a factory class to create a specific DAL object using dynamic class loading. This way I can create multiple DALs and switch them in and out using config values, meaning that I can use a static file-based DAL for unit testing, and then switch in the real SQL DAL class during end-to-end testing.

It's served me extremely well over the past year, and I now tend to do all my development by starting off with simple Xml based DALs for proof-of-concept work, before moving across to SQL versions, without having to change the business code. (Just for reference, the reason I did this in the first place was to decouple the development of the business components from the DAL components on a project - I wanted to finish the business logic whilst I was deciding whether I wanted to try developing with MySQL rather than SQL Server.)

Of course, I don't actually do much coding these days, so this may all be a bit old-hat. I'd also favour using an O/R tool these days (e.g. Genome) for data access.

e.g.
public abstract class CustomerManager
{
public CustomerManager(){}

// this logic presents client classes with a single method
// for updating / creating records, another personal favourite.
public void SaveCustomer(Customer customer)
{
if(customer.Id == -1)
CreateCustomer(customer);
else
UpdateCustomer(customer);
}

protected abstract int CreateCustomer(Customer customer);

protected abstract void UpdateCustomer(Customer customer);

public abstract Customer GetCustomer(int id);
}

public class XmlCustomerManager: CustomerManager
{
public CustomerManager(): base(){}

protected override int CreateCustomer(Customer customer){...}

protected override void UpdateCustomer(Customer customer){...}

public override Customer GetCustomer(int id){...}
}

public class CustomerManagerFactory
{
public static CustomerManager CreateCustomerManager()
{
Assembly assembly = Assembly.Load(AssemblyName, Version, Culture, PublicKeyToken);
object o = assembly.CreateInstance("XmlCustomerManager"); return (CustomerManager)o;
}
}

No comments: