Click here to Skip to main content
15,884,388 members
Articles / Operating Systems / Windows

Unit Testing with TestDriven.NET

Rate me:
Please Sign up or sign in to vote.
4.42/5 (10 votes)
5 Jan 2007CPOL4 min read 64.5K   43   15
Learn about unit testing from an expert.

Introduction

Unit testing is the automated testing of software components. The technique is used to build high-quality, reliable software by writing a suite of accompanying automated tests that validate assumptions and business requirements implemented by your software.

Learning to write good unit tests takes time, and it helps to have an experienced software developer guiding you. This article serves as an introduction to both the tools and techniques used in unit testing.

Prerequisites

Test Fixtures

Create a new project and copy the code below into a new class file.

Study the following code for a moment. The code implements a Test Fixture, which is a normal class decorated with the special attribute [TestFixture]. Test Fixtures contain Test Methods. Test Methods are decorated with the [Test] attribute. Other decorations, such as [TestFixtureSetup] and [TearDown], are used to decorate methods that have special meanings that will be explained later.

SampleFixture.cs
C#
using System;
using NUnit.Framework;

namespace UnitTest
{
    [TestFixture]
    public class SampleFixture
    {
        // Run once before any methods
        [TestFixtureSetUp]
        public void InitFixture()
        {
        }

        // Run once after all test methods
        [TestFixtureTearDown]
        public void TearDownFixture()
        {
        }

        // Run before each test method
        [SetUp]
        public void Init()
        {
        }

        // Run after each test method
        [TearDown]
        public void Teardown()
        {
        }

        // Example test method
        [Test]
        public void Add()
        {
            Assert.AreEqual(6, 5, "Expected Failure.");
        }

    }
}

Running a Test Fixture

You can right-click on any test fixture file and run it directly from Visual Studio .NET. This is the beauty of TestDriven.NET.

Image 1

Notice in your Error or Output tabs that a failure message appears.

Image 2

Double-clicking on the failure will take you to the precise line that failed. Correct this line so it will pass, then re-test the Fixture.

Image 3

Running a Test Method

You may also right-click anywhere inside a method and run just that one method.

Image 4

Image 5

Setup/Teardown Methods

If you have setup code that should be run once before any method or once after all methods, use the following attributed methods:

Image 6

If you have setup code that should run once before each method or once after each method in your fixture, use the following attributed methods:

Image 7

Tips on Writing Good Unit Tests

A proper unit test has these features:

  • Automated
  • No human input should be required for the test to run and pass. Often this means making use of configuration files that loop through various sets of input values to test everything that you would normally test by running your program over and over.

  • Unordered
  • Unit tests may be run in any order and often are. TestDriven.NET does not guarantee the order in which your fixtures or methods will execute, nor can you be sure that other programmers will know to run your tests in a certain order. If you have many methods sharing common setup or teardown code, use the setup/teardown methods shown above. Otherwise, everything should be contained in the method itself.

  • Self-sufficient
  • Unit tests should perform their own setup/teardown, and optionally may rely upon the setup/teardown methods described above. In no circumstances should a unit test require external setup, such as priming a database with specific values. If setup like that is required, the test method or fixture should do it.

  • Implementation-agnostic
  • Unit tests should validate and enforce business rules, not specific implementations. There is a fine line between the end of a requirement and the beginning of an implementation, yet it is obvious when you are squarely in one territory or the other. Business requirements have a unique smell: there is talk of customers, orders, and workflows. Implementation, on the other hand, smells very different: DataTables, Factories, and foreach() loops. If you find yourself writing unit tests that validate the structure of a Dictionary or a List object, there is a good chance you are testing implementation.

    Unit tests are designed to enforce requirements. Therefore, implementation tests enforce implementation requirements, which is generally a Bad Idea. Implementation is the part you don't care to keep forever. Depending on your skill level, implementations may change and evolve over time to become more efficient, more stable, more secure, etc. The last thing you need are unit tests yelling at you because you found a better way to implement a business solution.

    This advice runs counter to what you may read in other unit testing literature; most authors recommend testing all public methods of all classes. I find that while this is consistent with the goals of testing all code, it often forces tests that do more to enforce implementation than business requirements.

    Business requirements often follow a sequence or pattern, and my view is that the pattern is the real thing to be tested. Writing unit tests for every CustomerHelper class and OrderEntryReferralFactory class often indicates that classes and methods could be organized to better follow the business requirements, or at least wrapped in classes that reflect the requirements.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralA good start.... Pin
sgclark8-Jan-07 6:12
sgclark8-Jan-07 6:12 
GeneralRe: A good start.... Pin
Ben Allfree8-Jan-07 6:26
Ben Allfree8-Jan-07 6:26 
GeneralRe: A good start.... Pin
sgclark8-Jan-07 6:55
sgclark8-Jan-07 6:55 
GeneralRe: A good start.... Pin
Colin Angus Mackay8-Jan-07 6:45
Colin Angus Mackay8-Jan-07 6:45 
GeneralRe: A good start.... Pin
sgclark9-Jan-07 2:49
sgclark9-Jan-07 2:49 
GeneralHuh. Pin
roberthking8-Jan-07 4:21
roberthking8-Jan-07 4:21 
GeneralRe: Huh. Pin
Ben Allfree8-Jan-07 4:45
Ben Allfree8-Jan-07 4:45 
GeneralRe: Huh. Pin
Colin Angus Mackay8-Jan-07 6:41
Colin Angus Mackay8-Jan-07 6:41 
GeneralRe: Huh. Pin
roberthking8-Jan-07 6:41
roberthking8-Jan-07 6:41 
Ben,

As a general rule, I always try to remain tactful in these posts, and apologize for my tone, but I personally felt the "lead-in" to the article set expectations that were not met. You are completely correct that I should give you the benefit of the doubt, as you have stepped up to the plate and that is generally to be commended. However, as an avid browser of this and other such sites, I truly find it frustrating when articles appear that promise big and deliver small, and perhaps I am wrongly labelling this article as such.

I'm really not trying to pick a fight, but when I dove into an article from an "expert", I guess I expected something more. I've only been implementing TDD for about 6-7 months now, so my hope was that I would learn some expert tips to enhance my usage. Now that I know this article was the beginning of a series, I look forward to reading more.

Point #2: My point was that examples of Init & Teardown, however simple, would have illustrated the purpose of these more than simply stating that "If you have setup code that should be run", or somesuch. Recursive definitions don't shed enough light on the topic, IMO. Even a statement like "such as initializing the database to a known starting point" (or something like that) would help clarify their usage without adding complexity. See Peter Provost's article on here as an example of the depth I'm discussing. Again, I look forward to more depth on these in a later article.

Point #3: What I inferred from your comment "Unit tests should validate and enforce business rules, not specific implementation" and "Therefore, implementation tests enforce implementation requirements, which is generally a Bad Idea." was that writing unit tests for the low-level implementation was bad. If there's a flaw in my understanding of those points, I'm willing to be corrected.

Point #4: You are correct that your suggestions at the end are not to be found on the TDD software sites. Perhaps I was unclear in my comments about the tutorial, as that was what I was referring to.

It's my opinion that your suggestions tend to fly in the face of prevailing usage of these tools, as you also mentioned. I truly welcome more discussion of the different mindsets there, as it certainly is an interesting change from the conventional wisdom. Your approach seems to advocate testing at a higher level than what is commonly thought of as a "unit". While these "high-level" tests are of course important, HOW you arrive at the high-level result is equally important, IMO. Your reasoning regarding implementation changes is correct to a point, but prudent usage of unit testing would require that these rewritten implementations also have rewritten test harnesses that serve to validate the new logic.

It has been my experience that implementation tests are INVALUABLE when performing regression tests, as things like OS changes, database changes, etc. can and will break a tested app all too frequently.

In summary, I apologize for the tone of my initial post, and hope that my comments can be viewed as helpful suggestions.
GeneralRe: Huh. Pin
Ben Allfree8-Jan-07 7:42
Ben Allfree8-Jan-07 7:42 
GeneralRe: Huh. Pin
roberthking8-Jan-07 9:55
roberthking8-Jan-07 9:55 
GeneralRe: Huh. Pin
Ben Allfree8-Jan-07 13:19
Ben Allfree8-Jan-07 13:19 
GeneralArticle Pin
Stanislav Panasik18-Dec-06 21:02
Stanislav Panasik18-Dec-06 21:02 
GeneralRe: Article Pin
Ben Allfree18-Dec-06 21:08
Ben Allfree18-Dec-06 21:08 
GeneralRe: Article Pin
Stanislav Panasik19-Dec-06 1:45
Stanislav Panasik19-Dec-06 1:45 

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.