Click here to Skip to main content
11,932,103 members (60,552 online)
Click here to Skip to main content
Add your own
alternative version


57 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 1:45
memberdeadly.net15-Jul-08 1:45 
AnswerRe: NUnit: Practical Cases Pin
Brett Daniel15-Jul-08 5:02
memberBrett Daniel15-Jul-08 5:02 
GeneralRe: NUnit: Practical Cases Pin
deadly.net15-Jul-08 20:11
memberdeadly.net15-Jul-08 20:11 
AnswerRe: NUnit: Practical Cases Pin
Brett Daniel16-Jul-08 5:32
memberBrett Daniel16-Jul-08 5:32 
GeneralRe: NUnit: Practical Cases Pin
Kuldeep Vijaykumar16-Jul-08 22:34
memberKuldeep Vijaykumar16-Jul-08 22:34 
GeneralFew comments Pin
baruchl28-Aug-06 22:41
memberbaruchl28-Aug-06 22:41 
GeneralRe: Few comments Pin
Darren Pruitt29-Aug-06 5:57
memberDarren Pruitt29-Aug-06 5:57 
GeneralRe: Few comments [modified] Pin
Brett Daniel29-Aug-06 6:19
memberBrett Daniel29-Aug-06 6:19 
Excellent points, Busi.

I agree that fetching/saving objects can get expensive, especially when one needs a large collection. Sometimes lightweight placeholder objects can overcome this problem:

public class Lightweight<T> where T : PersistentObject {
    private int _uid;
    public Lightweight(int uid) {
        _uid = uid;
    public T GetHeavyweight<T>() where T : PersistentObject {
        return PersistentObject.Fetch<T>(_uid);

The benefit, of course, is type safety.

In the situation you mention, when one needs to pass a whole bunch of objects to or from the database, it is usually possible to implement some form of batch fetch/update in a collection object. I do not have a code example readily on hand, but the benefit, again, is type safety. I am reluctant to "[transform] a collection of such objects into a dataset" because we lose all information about the objects.

I agree with Darren; I always side with code clarity over test coverage. I have found that private methods are usually just helpers for public methods that one will test anyway. Usually one uses unit tests to verify that a class' public interface behaves as expected without worrying about the internals.

Sometimes, however, it is absolutely necessary to test a private method. In this case, the approach you outline would work assuming one also marks the test method with the same preprocessor code:

public void JustAMethodToTestTest() {

-- modified at 11:38 Tuesday 29th August, 2006
GeneralRe: Few comments Pin
DuncanJSmith21-Sep-07 13:57
memberDuncanJSmith21-Sep-07 13: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
Web03 | 2.8.151126.1 | Last Updated 26 Aug 2006
Article Copyright 2006 by Brett Daniel
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid