Mission Accomplished

by Randall 5/31/2009 8:21:00 AM

I started this blog with the sole intent of sharing things that I have found useful.  While I've not done a very good job of posting regularly, I've also purposely not posted things just to say that I 'post regularly'.

I was browsing my StatCounter statistics this morning, when I noticed something that made me smile.  Someone in a forum referred to my NUnit tutorial in their post as a good intro. 

 

(By the way, if you're unfamiliar with StatCounter, I suggest you check it out)

Currently rated 1.7 by 31 people

  • Currently 1.709677/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: ,

Getting Started With TestDriven.Net PlugIn & Unit Testing With NUnit Tutorial

by Randall 6/18/2008 10:42:00 AM

(You can download a Word version of this tutorial here.) 

In case you've been using NUnit (or some other testing framework) and NCover as stand-alone applications, you need to check out a plug-in for Visual Studio 2005/2008 called TestDriven.Net.

For those of you who have yet to even get started with Unit Testing - let's take a small project, create some unit tests, and show how this plug-in (along with NUnit and NCover) can increase the quality of your code.

*Disclaimer*  This demo will not implement the TDD methodology.  There is another school of thought that says you write a test before writing any code.  However, the purpose of this entry is to show you how easy it is to get started with unit testing.  How you choose to apply it to your projects will, of course, be left to you.  Furthermore, in-line SQL is used in this demo for purposes of simplicity and the connection string is embedded in the code.  Of course you'd ALWAYS want to put the connection string in your App.config or Web.config.  You know that.

Source code for the example project is located here (without the unit tests if you'd like to complete them youself) and the fully completed source code is available here.  The script for generating the single table is here - you'll simply need to create a database called 'DogsDB' and update your connection string.  I've marked the location of the connection string with a 'TODO' comment.  You can find that by looking in Visual Studio's Task List (hit CTL+T).

Dogs Application

So we have this little application that will allow us to manage all of the dogs in a kennel (or whatever).  We have all of our code in-place that will perform the normal CRUD operations and provide functionality to the UI.  How do we know that it will work?  I suppose before I stated doing unit testing I would simply have hooked in whatever user controls I needed and ran the application.  For really simple projects (such as this one) yes, that can work.  However, when you begin working on large-scale applications and creating very large APIs that will break other applications when they fail - unit testing will save your a$$.

At this point we'll be working with the unfinished sample.

If you haven't downloaded NUnit yet, you'll need to do that before going to the next step.  You can download it here

Let's add a new project to our solution - a class library - and call it 'UnitTestingDemo.UnitTests'.  This will be the container for all of our unit tests. 

 At this point, we'll need to add a reference to the NUnit framework.  So go ahead and add the reference - you'll find the asembly under the .Net tab of the 'Add Reference' dialog. 

Now we want to start writing tests for our business layer.  You could write tests that touch your DAL directly, but theoretically your BLL should expose all of your DAL.  However, if there are private methods within your DAL, you'll need to write tests to address those issues.  For this example, we'll just be testing the public layer which is in the BLL.

Let's go ahead and stub out our tests based on what's in the BLL.

In the project we just created, rename Class1.cs to DogLayerTests.cs and add a using statement for NUnit.Framework.  Your class should now look like this ... 

We want to base all of our tests on what's in our BLL.  One of the easiest ways to do this is to simply copy & paste our BLL methods into our test class. 

  1. remove all of the code from within each method
  2. rename each method to reflect that it's a test (add '_Test' to the end of each method's name)
  3. change every return type to void
  4. remove all parameters from each method's signature

When you're done, DogLayerTests should look like this ...

 

In order for NUnit to know that you want this class and its methods to constitute a suite of tests, you must 'decorate' the class and its methods with a couple of attributes.  At the class level, NUnit expects the [TestFixture] decorator and the [Test] decorator at the method level.  Go ahead and add those now.  Your code should now look like this ...

(If you don't see [Test] and [TestFixture] in IntelliSense, you either forgot to add the assembly reference to NUnit.Framework or you forgot to add the using for the namespace.) 

  Since we're starting with an empty database, the first thing we probably want to test is the ability to save a dog to the database.  Since NUnit will run your tests in alphabetical order, you can simply rename your methods numerically to fix that issue.  However, I think by definition each unit test should be able to run without any dependencies on other tests.  But if the database is empty, and you can't guarantee there will be any data to load, I'm not sure what else we can do.

I want to conduct my tests in the following order: Save, Get, Get All, Delete.  I'll letter my tests accordingly using A, B, C, & D as prefixes on the method names. 

Let's go ahead and write the test code for A_Dog_Save_Test.  We'll need to add two project references, one for the BLL and another for the Data Objects.  Do this and add your using statements for both namespaces. Because we're using the Color object in the System.Drawing namespace, you'll also need to add a reference to System.Drawing in your UnitTests project.  Go ahead and do that now also.

Once we start coding our test for the Save functionality, we can see that within that test, we'd like to have a way to verifiy that the object was saved.  Yes, the method itself will return a bool value, but we still want to know.  Based on that observation, we'll make the newly-created Dog a private member within the class so that other methods can perform tests based on it.

    [TestFixture]
    public class DogLayerTests
    {

        private DogLayer _dogLayer = new DogLayer();
        private Dog _dog = null;

        [Test]
        public void A_Dog_Save_Test()
        {

            this._dog = new Dog();
            this._dog.Id = System.Guid.NewGuid();
            this._dog.Name = "Fido";
            this._dog.Breed = "Mutt";
            this._dog.HairColor = System.Drawing.Color.Brown;
            this._dog.Age = 7;

            Assert.That(this._dogLayer.Dog_Save(this._dog) == true);

        }

    ...

    } 

We want all of the other tests to fail since they don't contain any code.  In order to make that work, insert the following into each of the remaining test methods.

Assert.That(true == false);

This will force the rest of our tests to fail; this is desirable. 

Now we'd like to run our test in order to determine if everything is working.  Since you have NUnit now, you can run your tests.  However, this article is about working with the TestDriven.Net plug-in, so you'll need to download that now.  You can download it here.  (Don't forget to save your solution and exit Visual Studio prior to running the installer.)  Once you have completed the installation, re-open the solution and continue.

Right-click on your test class and select 'Run Tests'. (We'll explore some of the other menu items later.)

 Your test will fail (there's an error that we'll flush out next). 

Debugging Using the Test Driven .Net Plug-In

Since our test failed and we have a bug, we want to be able to step through our test just like we'd normally step through using the debugger.  In order to do this, place a breakpoint at the test's call to the Save method.  Right-click on the test project again, but this time select 'Test With --> Debugger'.  You can now step into your code - eventually you'll find that we're missing a closing paren in our SQL insert statement.

Change

sb.AppendLine(string.Format("'{0}',", value.HairColor));

to

sb.AppendLine(string.Format("'{0}')", value.HairColor)); 


Re-run your tests.  The Save test will now pass and you'll see this in the Output window ... 

Congratulations.  You have just written your first unit test.

For the sake of brevity (too late, I know) here's the code for the rest of the test methods:

(Note that I have added more code in the first test to create multiple dogs which will be loaded by the Dogs_Get() test)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using NUnit.Framework;

using UnitTestingDemo.DataObjects;
using UnitTestingDemo.BLL;

namespace UnitTestingDemo.UnitTests
{

    [TestFixture]
    public class DogLayerTests
    {

        private DogLayer _dogLayer = new DogLayer();
        private Dog _dog = null;

        [Test]
        public void A_Dog_Save_Test()
        {

            this._dog = new Dog();
            this._dog.Id = System.Guid.NewGuid();
            this._dog.Name = "Fido";
            this._dog.Breed = "Mutt";
            this._dog.HairColor = System.Drawing.Color.Brown;
            this._dog.Age = 7;

            Assert.That(this._dogLayer.Dog_Save(this._dog) == true);

            // create 5 dogs and save them all
            for (int i = 0; i < 5; i++)
            {
                Dog tempDog = new Dog();
                tempDog.Id = System.Guid.NewGuid();
                tempDog.Name = string.Format("Name{0}", i.ToString());
                tempDog.Breed = string.Format("Breed{0}", i.ToString());
                tempDog.HairColor = System.Drawing.Color.Brown;
                tempDog.Age = i;

                Assert.That(this._dogLayer.Dog_Save(tempDog) == true);

            }

        }

        [Test]
        public void B_Dog_Get_Test()
        {

            Assert.That(this._dog != null);

            // attempt to retrieve the dog to ensure that it was saved ...
            Dog tempDog = this._dogLayer.Dog_Get(this._dog.Id);

            Assert.That(tempDog != null);

            // check as many properties as you'd like
            Assert.That(this._dog.Name == tempDog.Name);
            Assert.That(this._dog.Breed == tempDog.Breed);

        }

        [Test]
        public void C_Dogs_Get_Test()
        {
            List<Dog> dogs = this._dogLayer.Dogs_Get();
            Assert.That(dogs != null);
            Assert.That(dogs.Count > 0);
        }

        [Test]
        public void D_Dog_Delete_Test()
        {
            // get all of the dogs in the database
            List<Dog> dogs = this._dogLayer.Dogs_Get();
            Assert.That(dogs != null);
            Assert.That(dogs.Count > 0);

            // delete all of them
            foreach (Dog dog in dogs)
            {
                // delete the dog
                Assert.That(this._dogLayer.Dog_Delete(dog.Id) == true);

                // attempt to load the dog - it should fail (return null)
                Assert.That(this._dogLayer.Dog_Get(dog.Id) == null);
            }

        }

    }

}

Run your tests again.  Oh no.  Another bug (actually two within the same method).  This time I'll leave it up to you to decipher - but it's in the DAL. ;) (*Hint* Make sure your reader has rows before attempting to read from it and ensure that method will always return a value.)

Now that all of our tests have passed with flying colors, we're good to go, right?  Well yes and no.  Yes all of our tests passed, but how much of our code did we test?  Are we testing everything that will get called during its use?  This brings us to the next part.

Verifying Test Coverage With NCover

NCover is a code-coverage tool that is available as a stand-alone product but which is also packaged with the Test Driven .Net plug-in you downloaded earlier.  What exactly is code-coverage you ask?  Well, it's just a way of showing you whether or not your lines of code were actually used based on the tests you have written.  NCover will show you line-by-line what code was 'touched' and even provide a percentage of the lines of code within a method that were used by your test.  Determining the 'correct' percentage of appropriate code coverage is an entirely difference discussion on its own, but I attempt to get 100% coverage within my business and data access layers.  Some things, such as your data objects, might not get 100% coverage.  What is and isn't a good level of coverage will be left for you to research and determine.

Let's get started.

Since all of our tests passed, let's determine how good of a job we did in contructing our test classes (based on code coverage).  Right-click on your test project and select 'Test With --> Coverage'.  

NUnit will run the tests, then NCover will launch and show you the results. 

Looks pretty good, right?  I mean we have 100% in our business layer.  Remember though that this BLL isn't really doing anything in this application - it's really acting as a pass-through to our DAL.  Speaking of - we only have 29% coverage there.  Let's take a look and see what the problem is.

Looks like our BaseDal is skewing our results.  If you're like I am, you have a generic SQL base DAL from which all of your other object-specific DALs inherit.  This class should have been tested within some other solution (as a matter of fact, it was).  Since we've already unit tested that class in another test suite, we'll focus our efforts on obtaining 100% coverage within our DogDal.

Looks like everything is at 100% except for the Dog_Save method.  Let's take a look and see what the issue is ... 

Well, look at that.  We never attempted to update a previously-existing dog.  How do we know?  The red lines of code were never touched.  If you think back to our tests, we only saved new dogs - we never updated any of them.

While we thought we were ok because all of our tests passed, it's clearly evident now that we didn't do the best job we could when writing our tests.  This is why these tools are invaluable - because we all make mistakes.  True, our update method probably works - but you need to adopt the attitude of 'I want to KNOW that it works'.  Never assume anything, as you know. 

Let's go back and fix our tests.  In the first test where we were saving new dogs, let's add a few more lines of code that will update each of the dogs we just created and saved.  Your test method A_Dog_Save_Test should now look like this ...

        [Test]
        public void A_Dog_Save_Test()
        {

            this._dog = new Dog();
            this._dog.Id = System.Guid.NewGuid();
            this._dog.Name = "Fido";
            this._dog.Breed = "Mutt";
            this._dog.HairColor = System.Drawing.Color.Brown;
            this._dog.Age = 7;

            Assert.That(this._dogLayer.Dog_Save(this._dog) == true);

            // create 5 dogs and save them all
            List<Dog> dogs = new List<Dog>();
            for (int i = 0; i < 5; i++)
            {
                Dog tempDog = new Dog();
                tempDog.Id = System.Guid.NewGuid();
                tempDog.Name = string.Format("Name{0}", i.ToString());
                tempDog.Breed = string.Format("Breed{0}", i.ToString());
                tempDog.HairColor = System.Drawing.Color.Brown;
                tempDog.Age = i;

                Assert.That(this._dogLayer.Dog_Save(tempDog) == true);

                dogs.Add(tempDog);

            }

            // update all 5 of the newly-created dogs
            foreach (Dog dog in dogs)
            {
                dog.Name = string.Format("{0}_Updated", dog.Name);
                // save the dog - this will be an update now
                Assert.That(this._dogLayer.Dog_Save(dog) == true);
            }

        }

Re-run all of your tests using NCover as we just did.  They should all pass and NCover will show you the new results.  Drill down into the DogDal and look at the code coverage percentage ... 

Congratulations.  You now have 100% code coverage within your DogDal.  This is what we wanted.

Wrap-Up

While writing your unit tests, running them, fixing them, analyzing your code coverage, and adjusting your tests all takes time - the benefit in the long run will blow your mind.  As the weeks progress and you become more removed from the code you just wrote, having a series of tests to run will provide some much-needed peace of mind.  Developing software is tough enough without worrying about breaking code that you have previously written.

With that being said, I challenge you on your next project (or even your existing project(s) to implement unit testing.  It will change the way you develop software.  Frankly I don't know how I called myself a 'professional' developer before I began doing this.


kick it on DotNetKicks.com

Currently rated 1.4 by 25 people

  • Currently 1.44/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , , , , ,

Powered by BlogEngine.NET 1.3.1.0
Theme by Mads Kristensen

About the author

Name of author Randall Sexton
Currently a .Net developer for Bechtel Corporation in Oak Ridge, TN.

E-mail me Send mail

Calendar

<<  April 2014  >>
MoTuWeThFrSaSu
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

View posts in large calendar

Pages

    Recent comments

    Don't show

    Authors

    Categories


    Logo Credit

    My logo was taken from CodingHorror.
    Jeff Atwood © Copyright 2007

    Disclaimer

    The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

    © Copyright 2014

    Sign in