Click here to Skip to main content
12,560,765 members (49,012 online)
Click here to Skip to main content
Add your own
alternative version


58 bookmarked

An NUnit Test Suite Implementation

, 26 Aug 2006 Public Domain
Rate this:
Please Sign up or sign in to vote.
This article describes a scalable NUnit unit test suite for use on a tiered, database-driven .NET application.


In this article, I will describe a scalable NUnit unit test suite for use on a tiered, database-driven .NET application. The suite will define sample generators used to easily create dummy data for tests, and it will use test fixture inheritance for increased scalability and to allow for easy testing of common functionality.

I will focus on testing of the domain model of a sample application. This is usually located in the "middle" of an application and is often called the business logic layer (BLL). It uses the data access layer (DAL) to mediate data transfer to and from the database and drives the behavior of one or more user interfaces (UI) with which the user interacts or in which data is displayed.

This article assumes knowledge of .NET and C#, but does not require experience with unit testing or NUnit in particular.

BLL Implementation

In this sample application, classes in the BLL that implement database operations inherit from a base class called PersistentObject. This class defines the following interface[1]:

public abstract class PersistentObject {
    protected long _uid = long.MinValue;

    /// <span class="code-SummaryComment"><summary>

Say, for example, the application must save some client data and a client address that can be used elsewhere in the application. The BLL would therefore need to contain Address and Client classes derived from PersistentObject.

public class Address : PersistentObject {
    private string _streetAddress = null;
    private string _city = null;
    private string _state = null;
    private string _zip = null;

    public string StreetAddress {
        get { return _streetAddress; }
        set { _streetAddress = value; }

    public string City {
        get { return _city; }
        set { _city = value; }

    public string State {
        get { return _state; }
        set { _state = value; }

    public string Zip {
        get { return _zip; }
        set { _zip = value; }

    public override void Save() {
        // Call DAL to save fields
        // ...
    public override void Fill(long uid) {
        // Call DAL to fill fields
        // ...
    public override void Delete() {
        // Call DAL to delete object
        // ...

    /// <span class="code-SummaryComment"><summary>

Client is similar, except it contains a property that returns the Client's Address object.

public class Client : PersistentObject {
    private string _firstName = null;
    private string _lastName = null;
    private string _middleName = null;
    private long _addressUID = long.MinValue;

    private Address _addressObject;

    // ...

    public long AddressUID {
        get { return _addressUID; }
        set { _addressUID = value; }

    /// <span class="code-SummaryComment"><summary>

To save new client data, the user would do something like the following:

// Create the address that the client will link to
Address newAddress = new Address();
newAddress.StreetAddress = StreetAddressInput.Text;
newAddress.City = CityInput.Text;
newAddress.State = StateInput.Text;
newAddress.Zip = ZipInput.Text;
// Save the address to the database

// Create the client
Client newClient = new Client();
newClient.FirstName = FirstNameInput.Text;
newClient.MiddleName = MiddleNameInput.Text;
newClient.LastName = LastNameInput.Text;
// Link to the address
newClient.AddressUID = newAddress.UID;
// Save the client to the database

And to retrieve client data elsewhere in the application, the user would do something like the following:

Client existingClient = Client.Fetch(clientUID);
Address clientAddress = existingClient.Address;

Unit Testing Background

The BLL implementation outlined above is relatively standard. One can verify its behavior in any number of ways. The simplest but least robust is to test the UI. Since the UI depends on the BLL, one could conceivably verify the application by running through web pages or dialog boxes by hand. But what if the application has multiple UIs? Obviously, this method is slow, difficult to repeat, prone to human error, and may miss bugs. Also, it may promote bad programming practice in that a naïve coder may fix a symptom in the UI rather than the base cause in the BLL. This is not to say that we should omit UI testing, just that we should not rely on it to verify business logic.

A better option would be to create a simple driver program that calls the BLL method under development. This option would certainly be easier to repeat, but it may be difficult to save drivers for later or run all existing drivers to verify that nothing is broken.

This is where unit tests come in. One can think of a unit test as a simple driver program that one would probably write anyway. The unit testing framework organizes the tests, provides tools to make writing tests easier, and allows one to run tests in aggregate.

Test Suite Implementation

Since this article discusses a .NET application, I will use the NUnit testing framework in the example test suite. NUnit provides several features such as a test execution GUI, built-in assertions, and test attributes that make writing and running tests very easy.

It is most intuitive to create a test fixture (that is, a class containing a series of tests) for each class in the BLL. So, in keeping with the example, we will have ClientTest and AddressTest classes in the example test suite. These basic test fixtures will need to verify that data is added to the database, retrieved, edited, and deleted correctly. We often need to create dummy objects, so these test fixtures will also include some sample generators. Finally, we do not want to have to repeat common test code across many different test fixtures, so we will test the common database operations in a PersistentObjectTest class from which ClientTest and AddressTest both inherit.

I will explain the construction of PersistentObjectTest in parts. First, the class declaration:

/// <span class="code-SummaryComment"><summary>

This shows that PersistentObjectTest is a generic type that accepts the type of the object that its derived class tests. This type derives from PersistentObject and has an empty constructor. This lets us create sample generators and other utilities in a type-safe, generic manner:

#region Sample Generators

/// <span class="code-SummaryComment"><summary>

GetSample() simply returns a dummy object. The implementations of FillSample() and AssertIdentical() are delegated to the derived classes. These three methods are used by other test fixtures to create and test sample objects. The base class uses them to verify the basic database operations in the following test methods:

#region Data Tests

/// <span class="code-SummaryComment"><summary>

With PersistentObjectTest doing the heavy lifting, the concrete test classes need only define how to fill a sample object and how to check if two sample objects are identical. They can also define additional sample generators, utility functions, and test methods as needed.

public class AddressTest : PersistentObjectTest<Address> {

    public override void FillSample(Address sample) {
        Random r = new Random();
        string[] states = {"IL", "IN", "KY", "MI"};

        sample.City = "CITY" + DateTime.Now.Ticks.ToString();
        sample.State = states[r.Next(0, states.Length)];
        sample.StreetAddress = r.Next().ToString() + " Anywhere Street";
        sample.Zip = r.Next(0, 100000).ToString("00000");

    public override void AssertIdentical(Address expected, Address actual) {
        base.AssertIdentical(expected, actual);
        Assert.AreEqual(expected.City, actual.City, 
          "City does not match");
        Assert.AreEqual(expected.State, actual.State,
          "State does not match");
        Assert.AreEqual(expected.StreetAddress, actual.StreetAddress, 
          "StreetAddress does not match");
        Assert.AreEqual(expected.Zip, actual.Zip, 
          "Zip does not match");

public class ClientTest : PersistentObjectTest<Client> {

    public override void FillSample(Client sample) {
        sample.FirstName = "FIRST" + DateTime.Now.Ticks.ToString();
        sample.MiddleName = "MIDDLE" + DateTime.Now.Ticks.ToString();
        sample.LastName = "LAST" + DateTime.Now.Ticks.ToString();
        sample.AddressUID = new AddressTest().GetSample

    public override void AssertIdentical(Client expected, Client actual) {
        base.AssertIdentical(expected, actual);
        Assert.AreEqual(expected.FirstName, actual.FirstName, 
          "FirstName does not match");
        Assert.AreEqual(expected.MiddleName, actual.MiddleName, 
          "MiddleName does not match");
        Assert.AreEqual(expected.LastName, actual.LastName,
          "LastName does not match");
        Assert.AreEqual(expected.AddressUID, actual.AddressUID, 
          "AddressUID does not match");

ClientTest's sample generator uses AddressTest.GetSample() to create a dummy Address when filling a dummy sample Client. This general pattern is used often in this type of test suite. Any test that needs a dummy object simply calls the appropriate sample generator.

When running tests, NUnit looks for any classes marked with the attribute [TestFixture()]. It creates an instance of the class and runs any methods marked with the attribute [Test()]. The [ExpectedException()] attribute tells NUnit that the given method should throw the given exception. The test code itself uses NUnit's Assert object to verify that expected properties hold.

Any test fixture that inherits from an abstract base class also "inherits"[2] any test methods. Therefore, AddressTest, a concrete test fixture, inherits the SaveAndFetch(), EditAndFetch(), and Delete() test methods from PersistentObjectTest. Note that a derived class can override these test methods if, for example, its corresponding BLL class does not support deleting:

public override void Delete() {
    Assert.Ignore("This object does not support deleting");


Now that we have the basic test suite implemented, say the requirements change and we need to add a class representing a preferred client that receives discounts and special credit. First we will create a PreferredClient class derived from Client:

public class PreferredClient : Client {
    private double _discountRate = 1;
    private decimal _accountCredit = 0.00M;


    public override void Save() {
        // call DAL to save this object's fields



Next, we must create a PreferredClientTest test fixture derived from ClientTest. But this causes a problem: ClientTest inherits from PersistentObjectTest<Client>, but we need PreferredClientTest to inherit indirectly from PersistentObjectTest<PreferredClient> so that PersistentObjectTest's methods use the correct type of object. The solution is to move the generic signature "down the hierarchy" to ClientTest:

/// <span class="code-SummaryComment"><summary>

But we need to keep the non-generic tester so Client's tests will still run:

/// <span class="code-SummaryComment"><summary>

Finally, we define PreferredClientTest in terms of the generic version of ClientTest:

public class PreferredClientTest : ClientTest<PreferredClient> {

    public override void FillSample(PreferredClient sample) {
        Random r = new Random();
        // some random dollars and cents
        sample.AccountCredit = ((Decimal)r.Next()) + .25M; 
        sample.DiscountRate = r.NextDouble();

    public override void AssertIdentical
     (PreferredClient expected, PreferredClient actual) {
        base.AssertIdentical(expected, actual);
        Assert.AreEqual(expected.AccountCredit, actual.AccountCredit, 
          "AccountCredit does not match");
        Assert.AreEqual(expected.DiscountRate, actual.DiscountRate, 
          "DiscountRate does not match");

Note that the FillSample() and AssertIdentical() methods simply extend their base class counterparts. One can easily see how this type of expansion can continue as the application grows; it is simply a matter of adding a subclass and implementing the appropriate methods.


Primary Keys

This hypothetical test suite makes one glaring assumption: it assumes that PersistentObject is a valid base class for real-world classes. This assumption becomes most apparent in the Fetch/Fill methods which always take a long as a unique database identifier. Often, a real-world database will not be normalized such that all data has a bigint primary key (if only!). One can get around this problem by expanding the generic signature of PersistentObjectTest and PersistentObject.Fetch() to include the type of the derived class' unique identifier.

Dummy Data Overload

Because of its dependence on sample generators, the form of test suite creates a large amount of dummy data in the database. This is acceptable since a large part of testing a database-driven application is verifying that data is saved and retrieved correctly. However, it means that the development application must have a dedicated testing database server that is regularly reset to some known state to prevent dummy data from overshadowing valid data. Also, the recursive nature of the sample generators may make it possible to get into a never-ending sample generation cycle that could very quickly bring a database (not to mention the stack frame) to its knees.


The implementation I have outlined assumes that random dummy data will often suffice for most tests that use the generated objects. In other words, the consumer of the sample object must ensure that a generated object meets the desired preconditions. Bounds on randomness can often be achieved with parameterized sample generators such as the following:

/// <span class="code-SummaryComment"><summary>

However, there is no general, easily-implemented way for the sample generators to control randomness or return a bounded exhaustive list of all possible samples. In fact, exhaustive test generation is an ongoing research problem.


The hypothetical test suite architecture that I have outlined is useful for testing tiered, database-driven applications in which reasonable, random sample data is often needed. By using test fixture inheritance and sample generators, it becomes very easy to expand the test suite as the application grows. It also reduces the amount of code needed to test the most important aspect of a database-driven application: that data travels to and from the database correctly. Variations on this testing implementation have performed well for several .NET applications with several dozen to several thousand classes.


  1. In reality, Save, Fill, and Delete would usually wrap protected overridable methods like DoSave, DoFill, and DoDelete. This would allow the base class to define common pre- and post-database operation steps while leaving the derived class to handle its own data. Also, Delete would usually set an "Ignore" flag rather than completely remove the data from the database. Regardless, we can ignore those complications in this article. Just assume that a derived class would override Save, Fill, and Delete in the obvious manner if the class supports the appropriate database operation.
  2. This is not true inheritance. NUnit uses reflection to find any methods marked with the attribute [Test()] regardless of where the method occurs in the class hierarchy. Also, overriding a test method does not retain the [Test()] attribute.


This article, along with any associated source code and files, is licensed under A Public Domain dedication


About the Author

Brett Daniel
Web Developer
United States United States
Brett Daniel is a graduate computer science student at the University of Illinois Urbana-Champaign. His work focuses on software engineering, application architecture, software testing, and programming languages.

You may also be interested in...


Comments and Discussions

GeneralNUnit: Practical Cases Pin
deadly.net15-Jul-08 0:45
memberdeadly.net15-Jul-08 0:45 
AnswerRe: NUnit: Practical Cases Pin
Brett Daniel15-Jul-08 4:02
memberBrett Daniel15-Jul-08 4:02 
This article is primarily concerned with testing that the object-relational mapping is sound. Testing that the business logic is correct is separate matter, but, like you observe, it probably depends on the database. I would speculate that to sufficiently test your BLL, you will almost certainly require database calls, but it is best to minimize the database interaction. That is, you should separate BLL code from database code so that you can test as much business logic as possible without touching the database.

Since I originally wrote this article, object-relational mapping libraries such as NHibernate and others have gained popularity and are almost certainly the preferred way to persist object data. These libraries do not remove the need to test object-relational mapping (since one must still verify that the mappings are correct), but they remove much of the boilerplate code. They also make it easier to test persistent data since 1) they can automatically check that the database schema matches the class structure, and 2) one can easily configure the application to use separate databases for testing and production. There are many articles online that describe this process; just search for "testing hibernate".

All this does not answer your question, "What (not) to test?" I have always taken the following pragmatic, perhaps glib, approach to testing: test as much as you can, starting with what is most likely to break. This means you should probably spend most of your time verifying complex business logic, not that your one-line getter properties return the same values you gave to their setters (which is the essence of database persistence). Thus, you should create a testing architecture that minimizes the time required to verify that mappings are correct. This is where a persistence library and an architecture roughly like the one I described in the article becomes useful. With such an architecture in place, you can focus on testing more important code.
GeneralRe: NUnit: Practical Cases Pin
deadly.net15-Jul-08 19:11
memberdeadly.net15-Jul-08 19:11 
AnswerRe: NUnit: Practical Cases Pin
Brett Daniel16-Jul-08 4:32
memberBrett Daniel16-Jul-08 4:32 
GeneralRe: NUnit: Practical Cases Pin
Kuldeep Vijaykumar16-Jul-08 21:34
memberKuldeep Vijaykumar16-Jul-08 21:34 
GeneralFew comments Pin
baruchl28-Aug-06 21:41
memberbaruchl28-Aug-06 21:41 
GeneralRe: Few comments Pin
Darren Pruitt29-Aug-06 4:57
memberDarren Pruitt29-Aug-06 4:57 
GeneralRe: Few comments [modified] Pin
Brett Daniel29-Aug-06 5:19
memberBrett Daniel29-Aug-06 5:19 
GeneralRe: Few comments Pin
DuncanJSmith21-Sep-07 12:57
memberDuncanJSmith21-Sep-07 12:57 

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.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.161026.1 | Last Updated 26 Aug 2006
Article Copyright 2006 by Brett Daniel
Everything else Copyright © CodeProject, 1999-2016
Layout: fixed | fluid