Click here to Skip to main content
Click here to Skip to main content

Data Aware Unit Testing

By , 10 Oct 2006
 

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.

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:

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

About the Author

cgreen69
Software Developer (Senior)
United Kingdom United Kingdom
Member
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.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralA nice tool that can help on the mattermemberegozi1317 Oct '06 - 12:28 
It's not about mocking (not stubbing), but it helps a lot with db testing.
The tool is about retaining db state between tests.
It can be found here: http://www.codeplex.com/Release/ProjectReleases.aspx?ProjectName=MassDataHandler[^]
 
I'm not a par of the project, but I've heard about it on Roy Osherov's excellent blog, at http://weblogs.asp.net/rosherove[^]
 

 
Ken Egozi,
http://www.kenegozi.com/blog
GeneralAdd in a DAO FactorymemberAkaitatsu17 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 Factorymembercgreen6917 Oct '06 - 4:47 
Yes, I think you have a point there. If I can find some time I might try and code that up.
GeneralA good startmembercraigg7517 Oct '06 - 4:17 
I've worked with unit testing for quite some time. I like your approach and is very similar to the approach that I had sort of come up in my head. However I never went forward with it because I didn't like have to "code my database schema" as you have in your MockDAO (sorry Tim, the differences between mocks and stubs are too minor in this context to quibble). I've looked long and hard for a codegen tool that would scan my database schema to generate the MockDAO automatically. That way if I make a change in the schema I don't have to remember to go back and update all of the mockDAO code. Code gen'ng your database schema is possible however some of the real world database objects are the results of fancy joins. That kind of code generation I've never run across. Perhaps some upfront design would lessen the need for this (perhaps using Views instead of stored procs to handle the fancy joins). Anyway this is a good discussion and hopefully people will have some constructive thoughts.
GeneralRe: A good startmembercgreen6917 Oct '06 - 4:56 
Thank you for your comments.
 
Admittedly the coding of the database schema is time consuming and can be a bit of a drag. I try to take the pain out of it by doing a query against the database in the concrete class (using complex joins if necessary) and saving the schema to the file system, then using XSD to generate the classes I need.
 
However as you point out, one still has to go in and mock up the data and of course should the database change then it has to be redone - so its a technique thats probably suited to a little later in the development cycle when table design has stabilised a little. Like the concreate classes I'd expect the tests to evolve over time and at the start of the development cycle they'd be a lot less functional.
 

GeneralRe: A good startmemberAkaitatsu17 Oct '06 - 6:10 
This article has sparked some excellent discussion and several good ideas. CodeSmith (http://www.codesmithtools.com) comes with a template for generating an XSD for a SQL Server DB schema. It shouldn't be hard to write another template that read the first row out of each fact table and the related information in the dimension/lookup tables to create some basic mock or stub code.
 
- Bryant
GeneralMocks aren't stubsmemberTimMerksem16 Oct '06 - 22:24 
why are there soo many people that don't see the difference between stubs and mocks, in the DAO example you use a stub, not a mock !
 
please god, sort them out !
GeneralRe: Mocks aren't stubsmembercgreen6916 Oct '06 - 22:36 
Tim,
 
If you look around the internet you'll see numerous discussions relating to what is a mock and what is a stub. Most of them contradictory. If my naming offends you then I apologise.
 
I am not going to be drawn into a highly theoretical discussion of what composes a mock and what composes a stub I simply present an approach that works well for me and hopefully others. Feel free to rename the mocks to stubs if it makes the code easier for you to understand.
 

GeneralRe: Mocks aren't stubsmemberTimMerksem16 Oct '06 - 23:06 
When u use a stub you do black box testing of a code part, when you use a mock you do interaction based (white box)testing. Mocking is about predictions as where stubs are about boundaries to simplify tests.
 
I apologize for the fact that I have difficulties accepting that there are people who really don't have a clue of mock based testing versus state based testing.
 
It's a pitty that codeproject isn't a wiki, otherwise you could learn and understand what mock based testing is all about.
GeneralRe: Mocks aren't stubsmembercgreen6917 Oct '06 - 1:23 
Tim,
 
Lets try and be professional here.
 
I've penned this article as a learning experience both for myself and for others. Frankly I'm getting a little tired of people throwing their toys out of their prams with child like comments such as 'please god, sort them out'. By all means make constructive criticism, by all means disagree but lets try and move the discussion forward like adults that way we all learn.
 
I welcome the first part of your reply providing a definition of your interpretation of white and black box testing as its the first time you've contributed anything worthwhile even though I don't agree.
 
I'm sure you will agree that mocking and stubbing are evolving techniques and I don't recognise the definitions you provide. For me white box testing is impossible against a Mock as a white box test by its very definition is a fine grained test against a specific piece of functionality hence the need for at least partial code implementation.
 
The term Mock is used by vendors of third party libraries which can spit out 'mocks' based on existing concrete classes, then as you correctly point out one can set up prediction based testing. However for me as I have already stated that is insufficient, I prefer to inherit from concrete classes and do my testing that way.
 
Maybe you'd like to contribute some of your own code that way readers can evaluate both approaches and decide which they prefer and which fits their projects best.
 

 

 

 

GeneralRe: Mocks aren't stubsmemberTimMerksem17 Oct '06 - 3:24 
A quote,
 
As I said at the beginning, mocks are often confused with stubs. This is quite easy to understand since the various tools you can obtain to create mocks are perfect for writing stubs. The important difference is not what they are but how they are used.
Stubs are typically used to stub out objects that are expensive to create or manipulate. A classic example might be a database connection. As a result you tend to most often find stubs at the edges of a system, or around complex clusters of objects within a system. To create a stub you form an alternative implementation of an interface and replace the real methods with simple canned data.
 
The key difference with mocks is the expectation setting mechanism where you test which methods were called on the mock. Mockists often refer to a mock object that just returns values as 'just a stub'. So one way of looking at the difference between mocks and stubs is that with mocks you set and test expectations as part of your test. As it turns out, that's a little simplistic - I've certainly written stubs that do some simple form of expectation checking such as setting a boolean field if a certain method is called. However I think it's reasonable to say that expectations are a rare feature for stubs but the primary feature for mocks.
 
The biggest issue isn't really the difference between mocks and stubs. It's the interaction vs state style that's most interesting. Interaction-based testers write mocks for all secondary objects. State-based testers write stubs only for those objects where it's not practical to use the real object - external services, things that are expensive, and some things that are awkward to test in a state-based way such as a cache.
GeneralRe: Mocks aren't stubsmembercgreen6917 Oct '06 - 4:39 
Thank you for that.
 
I'm pleased we agree that the biggest issue isn't really the difference between mocks and stubs.
 
In my opinion, and its just my opinion I'm not hugely interested in a debate over interaction / state based testing, I'm more concerned with a testing mechanism that has worked for me in the past and continues to do so which is what I have presented here.
 
Having personally witnessed a multi million pound project fail in the city of London (and having been informed of another) due to strict interpretation and over reliance on expectation based Mocking and having endured countless debates with consultants over testing theory whilst deadlines are missed you'll find me unrepentant if what I deem to be Mock testing is at odds with the definitions of others.
 
I accept however that your experiences may be different and I hope that your approach continues to work just as well on your projects.
QuestionAm I missing something?memberBil Irving10 Oct '06 - 8:41 
In the code you posted, there doesn't seem to be a MockStudent() method. Bit of a bummer really, as that's what the article is about. Laugh | :laugh:

AnswerRe: Am I missing something?membercgreen6910 Oct '06 - 9:23 
Damm this html editor - its omitted some of the code. Thanks for pointing it out. Will rectify

GeneralRe: Am I missing something?memberBil Irving10 Oct '06 - 20:21 
Great. Makes sense now, cheers. Nice article. There's not a lot of info on mock objects on the net that I've found, and we need to use them for our current project.

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

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130516.1 | Last Updated 10 Oct 2006
Article Copyright 2006 by cgreen69
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid