Click here to Skip to main content
15,867,594 members
Articles / Programming Languages / C#
Article

Data Aware Unit Testing

Rate me:
Please Sign up or sign in to vote.
4.39/5 (11 votes)
10 Oct 20064 min read 52K   30   15
An article discussing strategies for unit testing in data aware environments.

Introduction

Unit tests are all the rage these days and I am a big fan; however, I think there is a danger on over reliance on unit tests and more importantly writing unit tests which have little or no value. I've seen one major project and heard of several others fail due to over zealous consultants writing reams of unit tests which have little real value. At the end of the lifecycle the client is left with a semi functioning application and an impressive collection of green lights in the testing tool.

One thing I commonly see done in a less than optimal manner is database unit testing; this to me is not unit testing, it is integration testing. Don't get me wrong, integration testing is an essential process during a project life cycle, but unit tests should be completely encapsulated with no unnecessary dependencies and no footprint, i.e., no lasting alteration to a data source, be it a database, file, or any other storage medium.

When I deploy my unit tests on my testing server, I don't want to have to permit that server for access to a database and all the associated headaches involved. I also don’t want tests manipulating data that may in turn effect the results of other tests – that’s for the integration tests.

I've often been curious as to how to perform meaningful unit tests on code which has some sort of data dependency, and let's face it, 90% of code we write will probably need to communicate with a database at some point during its life cycle. Here I present my thoughts, I hope this will trigger discussion as to the best way to tackle data testing or at the very least provide you with some food for thought.

Mock your world

Mocking objects in unit tests is a useful technique to test the core logic of the class whilst removing any process that may have a heavy or unnecessary overhead such as performing a complex join against a database. There are many third party tools to help you mock objects but personally I find them buggy and unintuitive therefore I do my own mocking which is remarkably straightforward.

The following is an example of a data centric unit testing approach.

C#
public abstract class GradingProcess
{
    protected int _id;
    protected string _connectionProperties = null;

    public void Process()
    {
        this._connectionProperties = GetConnectionString();
        DataSet rawGrades = GetData();
        this.CalculateGrades(rawGrades);
    }

    public void CalculateGrades(DataSet rawGrades)
    {
        //...iterate through and do
        //   something useful with the data 
    }

    protected abstract string GetConnectionString();

    protected DataSet GetData()
    {
        SqlCommand comm = 
          new SqlCommand("select * from grades where id=@id");
        SqlConnection conn = 
          new SqlConnection(this._connectionProperties);
        comm.Connection = conn;
        comm.Parameters.Add(new SqlParameter("@id", this._id));
        SqlDataAdapter sda = new SqlDataAdapter(comm);
        DataSet ds = new DataSet();

        try
        {
            conn.Open();
            sda.Fill(ds);
        } 
        finally
        {
            conn.Close();
        }

        return ds;
    }
}


   
    public class Student : GradingProcess
    {
        protected override string GetConnectionString()
        {
            return "production server settings";
        }
    }

    public class MockStudent : GradingProcess
    {
        protected override string GetConnectionString()
        {
            return "dev server settings";
        }
    }

[Test]
public void CanWeGrade()
{
    GradingProcess mock = new MockStudent();

    mock.Process();
}

The above example is hypothetical but is used to demonstrate an approach I’ve seen used in unit tests. The student class is the concrete class that would be used in our application, the MockStudent class is used solely within our unit test environment. Notice that the mock classes conform to an abstract class that controls the process flow in the sub classes.

For the purpose of this article, the classes are lumped together. In the real world I would expect the abstract classes to be in an assembly referenced by the unit tests and the application we are writing. This test doesn’t achieve much other than calling the process method but I want to focus on the structure of the code. If you were doing this for real you’d test properties of the mock class to ensure results were correct.

What I don’t like about this is it simply swaps one dependency for another, in this case the development database for the production database. The above test is what I’d refer to as an integration test and as such has value in the integration testing phase but is not really suited to unit testing.

Enter the DrAgOn

The data access object (DAO) is a neat way of encapsulating data access to help us on our unit testing mission. Its responsible for all interaction with the data source, the calling class doesn’t care where the data is coming from it simply talks to the DAO.

Please take a look at the following code:

C#
public interface IDAO
{
    DataSet GetData(int id);
} 

public class MockDAO : IDAO
{
    #region IDAO Members
    public DataSet GetData(int id)
    {
        DataSet ds = new DataSet(); 
        ds.Tables.Add(new DataTable("grades"));
        ds.Tables[0].Columns.Add(new DataColumn("Name"));
        ds.Tables[0].Columns.Add(new DataColumn("Result"));
        DataRow row = ds.Tables[0].NewRow();
        row["name"] = "John Jones";
        row["result"] = "90%";
        ds.Tables[0].Rows.Add(row);
        return ds;
    }
    #endregion
}

    public class ConcreteDAO : IDAO
    {
        private string _connectionProperties = 
                        "prod server settings";

        public DataSet GetData(int id)
        {
            SqlCommand comm = 
              new SqlCommand("select * from grades where id=@id");
            SqlConnection conn = 
              new SqlConnection(this._connectionProperties);
            comm.Connection = conn;
            comm.Parameters.Add(new SqlParameter("@id", id));
            SqlDataAdapter sda = new SqlDataAdapter(comm);
            DataSet ds = new DataSet();
            sda.Fill(ds);
            SqlDataReader studentGrades = null;

            try
            {
                conn.Open();
                studentGrades = comm.ExecuteReader();
            } 
            finally
            {
                conn.Close();
            }

            return ds;
        }
    }


    public class Student 
    {
        IDAO _dataLayer;
        private int _id;

        public Student(IDAO dao)
        {
            this._dataLayer = dao;
        }

        public void Process()
        {
            DataSet rawGrades = 
              this._dataLayer.GetData(this._id);
            this.CalculateGrades(rawGrades);
        }

        private void CalculateGrades(DataSet rawGrades)
        {
            ...do something useful
        }
    }
    [Test]
    public void CanWeGrade()
    {
        IDAO mock = new MockDAO();
        Student student = new Student(mock);
        student.Process();
    }

If you compare the two code snippets you’ll notice that we no longer require a mock student class rather we mock the DAO. The DAO has to conform to an interface (IDAO) which its concrete equivalent also has to conform to. In order to test we instantiate a concrete student class and pass in the mock and call the process method. The beauty of this approach is we have complete control over the mock data and can use it to test a variety of scenarios.

For simplicity, I’ve again combined all the classes together.

Conclusion

Sensible unit testing is a valuable tool in any developers arsenal. Research suggests that used properly it can reduce bugs in production by up to 30%. Database dependent class testing is something I have seen done badly or completely skipped, I hope this article provides some help and If the response is favourable I may develop the idea further.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Software Developer (Senior)
United Kingdom United Kingdom
A .net devotee specialising in Object Orientated web development for financial institutions in Europe. When not working can normally be found at a bar within walking distance of the office.

Comments and Discussions

 
GeneralA nice tool that can help on the matter Pin
egozi1317-Oct-06 12:28
egozi1317-Oct-06 12:28 
GeneralAdd in a DAO Factory Pin
Akaitatsu17-Oct-06 4:19
Akaitatsu17-Oct-06 4:19 
I would consider adding an abstract DAO class with a factory method that grabs the correct DAO implementation and hands it back to the Business layer. The Business layer should not have to figure out which DAO implementation to use.

- Bryant
GeneralRe: Add in a DAO Factory Pin
cgreen6917-Oct-06 4:47
cgreen6917-Oct-06 4:47 
GeneralA good start Pin
craigg7517-Oct-06 4:17
craigg7517-Oct-06 4:17 
GeneralRe: A good start Pin
cgreen6917-Oct-06 4:56
cgreen6917-Oct-06 4:56 
GeneralRe: A good start Pin
Akaitatsu17-Oct-06 6:10
Akaitatsu17-Oct-06 6:10 
GeneralMocks aren't stubs Pin
TimMerksem16-Oct-06 22:24
TimMerksem16-Oct-06 22:24 
GeneralRe: Mocks aren't stubs Pin
cgreen6916-Oct-06 22:36
cgreen6916-Oct-06 22:36 
GeneralRe: Mocks aren't stubs Pin
TimMerksem16-Oct-06 23:06
TimMerksem16-Oct-06 23:06 
GeneralRe: Mocks aren't stubs Pin
cgreen6917-Oct-06 1:23
cgreen6917-Oct-06 1:23 
GeneralRe: Mocks aren't stubs Pin
TimMerksem17-Oct-06 3:24
TimMerksem17-Oct-06 3:24 
GeneralRe: Mocks aren't stubs Pin
cgreen6917-Oct-06 4:39
cgreen6917-Oct-06 4:39 
QuestionAm I missing something? Pin
StockportJambo10-Oct-06 8:41
StockportJambo10-Oct-06 8:41 
AnswerRe: Am I missing something? Pin
cgreen6910-Oct-06 9:23
cgreen6910-Oct-06 9:23 
GeneralRe: Am I missing something? Pin
StockportJambo10-Oct-06 20:21
StockportJambo10-Oct-06 20:21 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.