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

A quick overview of NUnit tests

, 31 Mar 2014
Rate this:
Please Sign up or sign in to vote.
how to use NUnit to code tests

Introduction

In this article we will consider the different ways of testing a class.

The environment used is :

  • NUnit 2.6.2
  • Resharper to run the tests

Tested Code

public sealed class Calculator
{
    public int Divide(int a, int b)
    {
        return a / b;
    }
}

During this project we will test our calculator for three test cases:

  • 1/1 = 1
  • 2/1 = 2
  • 1/0 is not valid

Naïve solution

[TestFixture]
public sealed class CalculatorTest1
{
    private Calculator _calculator;
 
    [SetUp]
    public void Setup()
    {
        _calculator = new Calculator();
    }
 
    [Test]
    public void OneDividedByOne()
    {
        int result = _calculator.Divide(1, 1);
        Assert.AreEqual(1, result);
    }
 
    [Test]
    public void TwoDividedByOne()
    {
        int result = _calculator.Divide(2, 1);
        Assert.AreEqual(2, result);
    }
 
    [Test]
    public void OneDividedByZero()
    {
        Assert.Throws<DivideByZeroException>(() => _calculator.Divide(1, 0));
    }
}

Here is the naïve solution you may have coded before reading NUnit documentation.

Let's try to make it a bit better.

Using the TestCase attribute

[TestFixture]
public sealed class CalculatorTest2
{
    [SetUp]
    public void Setup()
    {
        _calculator = new Calculator();
    }

    private Calculator _calculator;

    [TestCase(1, 1, ExpectedResult = 1, TestName = "OneDividedByOne")]
    [TestCase(2, 1, ExpectedResult = 2, TestName = "TwoDividedByOne")]
    [TestCase(1, 0, ExpectedResult = 0, ExpectedException = typeof (DivideByZeroException), TestName = "OneDividedByZero")]
    public int CalculatorTestMethod(int firstNumber, int secondNumer)
    {
        return _calculator.Divide(firstNumber, secondNumer);
    }
} 

The use of the TestCase attribute make the code shorter grouping all the cases in one method.

This way of coding tests seems to be really adapted to test our Calculator .

Adding a new TestCase is really quick and clear.

However if your tested method takes reference types as parameters which is not a string, you can't use the TestCase attribute.

Using the TestCaseSource attribute

[TestFixture]
public sealed class CalculatorTest3
{
    private Calculator _calculator;
 
    [SetUp]
    public void Setup()
    {
        _calculator = new Calculator();
    }
 
    [TestCaseSource(typeof(CalculatorTest3TestCaseDataFactory), "TestCases")]
    public void CalculatorTestMethod(int firstNumber, int secondNumer, int expectedResult)
    {
        var result = _calculator.Divide(firstNumber, secondNumer);
        Assert.AreEqual(expectedResult, result,"A meaning description to help if test crashes");
    }
}

With the associated test case data factory

public class CalculatorTest3TestCaseDataFactory
{
    public static IEnumerable TestCases
    {
        get
        {
            yield return new TestCaseData(1, 1, 1).SetName("OneDividedByOne");
            yield return new TestCaseData(2, 1, 2).SetName("TwoDividedByOne");
            yield return new TestCaseData(1, 0, default(int))
                .Throws(typeof(DivideByZeroException))
                .SetName("OneDividedByZero");
        }
    }
}

This implementation allows to divide the concern :

  • CalculatorTest3 containing the way to test data
  • CalculatorTest3TestCaseDataFactory providing the data

In our trivial example we must have used the Returns() method to set our expected result.

Let me tell you why I won't use that in the case my tested class is returning a reference type MyClass I coded :

  • NUnit will use the MyClass.Equals and maybe I want to compare my objects differently
  • I can add meaning descriptions coding my own comparison in the TestClass

Typing our data

[TestFixture]
public sealed class CalculatorTest4
{
    private Calculator _calculator;
 
    [SetUp]
    public void Setup()
    {
        _calculator = new Calculator();
    }
 
    [TestCaseSource(typeof(CalculatorTestCaseData4), "GetTestCases")]
    public void CalculatorTestMethod(CalculatorTestCaseData4 testCase)
    {
        int result = _calculator.Divide(testCase.FirstNumber, testCase.SecondNumber);
        Assert.AreEqual(testCase.ExpectedResult, result);
    }
}

To use typed data let's add some properties containing input values and expected result

public class CalculatorTestCaseData4
{
    public int FirstNumber { get; private set; }
    public int SecondNumber { get; private set; }
    public int ExpectedResult { get; private set; }
 
    public IEnumerable GetTestCases
    {
        get
        {
            yield return new TestCaseData(OneDividedByOne()).SetName("OneDividedByOne");
            yield return new TestCaseData(TwoDividedByOne()).SetName("TwoDividedByOne");
            yield return new TestCaseData(OneDividedByZero())
                .Throws(typeof(DivideByZeroException))
                .SetName("OneDividedByZero");
        }
    }
 
    private CalculatorTestCaseData4 OneDividedByOne()
    {
        return new CalculatorTestCaseData4
            {
                FirstNumber = 1,
                SecondNumber = 1,
                ExpectedResult = 1
            };
    }
 
    private CalculatorTestCaseData4 TwoDividedByOne()
    {
        return new CalculatorTestCaseData4
            {
                FirstNumber = 2,
                SecondNumber = 1,
                ExpectedResult = 2
            };
    }
 
    private CalculatorTestCaseData4 OneDividedByZero()
    {
        return new CalculatorTestCaseData4
            {
                FirstNumber = 1,
                SecondNumber = 0
            };
    }
}

We are now manipulating typed data, the test cases are much more meaningful than in previous example.

One drawback in this example is that test case names and method names are the same, unfortunately it's not refactoring proof (you got to change each one if you want to change your names).

Let's fix it with the next example.

Make it a bit better

[TestFixture]
public sealed class CalculatorTest5
{
    private Calculator _calculator;
 
    [SetUp]
    public void Setup()
    {
        _calculator = new Calculator();
    }
 
    [TestCaseSource(typeof(CalculatorTestCaseData5), "GetTestCases")]
    public void CalculatorTestMethod(CalculatorTestCaseData5 testCase)
    {
        int result = _calculator.Divide(testCase.FirstNumber, testCase.SecondNumber);
        Assert.AreEqual(testCase.ExpectedResult, result);
    }
}

With the data factory

public class CalculatorTestCaseData5
{
    public int FirstNumber { get; private set; }
    public int SecondNumber { get; private set; }
    public int ExpectedResult { get; private set; }
 
    private static readonly TestCaseDataFactory<CalculatorTestCaseData5> TestCaseDataFactory = new TestCaseDataFactory<CalculatorTestCaseData5>();
 
    public IEnumerable GetTestCases
    {
        get
        {
            yield return OneDividedByOne();
            yield return TwoDividedByOne();
            yield return OneDividedByZero().Throws(typeof(DivideByZeroException));
        }
    }
 
    private TestCaseData OneDividedByOne()
    {
        var calculatorTCD = new CalculatorTestCaseData5
        {
            FirstNumber = 1,
            SecondNumber = 1,
            ExpectedResult = 1
        };
        return TestCaseDataFactory.Get(calculatorTCD);
    }
 
    private TestCaseData TwoDividedByOne()
    {
        var calculatorTCD = new CalculatorTestCaseData5
        {
            FirstNumber = 2,
            SecondNumber = 1,
            ExpectedResult = 2
        };
        return TestCaseDataFactory.Get(calculatorTCD);
    }
 
    private TestCaseData OneDividedByZero()
    {
        var calculatorTCD = new CalculatorTestCaseData5
        {
            FirstNumber = 1,
            SecondNumber = 0
        };
        return TestCaseDataFactory.Get(calculatorTCD);
    }
}

And the TestCaseDataFactory

public sealed class TestCaseDataFactory<T>
{
    public TestCaseData Get(T data, [CallerMemberName] string memberName = "noName")
    {
        return new TestCaseData(data).SetName(memberName);
    }
}

We got all the advantages of previous version with no repetition for names. You will find attached a Resharper template for making this version of testing.

Output of our tests execution

Conclusion

We have seen five versions for coding unit tests with NUnit.

So now you got no more excuses to skip coding tests.

License

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

About the Author

G.TR
Software Developer
France France
No Biography provided
Follow on   LinkedIn

Comments and Discussions

 
QuestionVery good article! PinprofessionalVolynsky Alex4-Feb-14 9:35 
Questiontest file cannot be downloaded Pinmemberfredatcodeproject29-Jan-14 3:58 
AnswerRe: test file cannot be downloaded Pinmemberpipo4429-Jan-14 4:39 
GeneralRe: test file cannot be downloaded Pinmemberfredatcodeproject29-Jan-14 4:48 
GeneralRe: test file cannot be downloaded Pinmemberpipo4429-Jan-14 5:03 
GeneralRe: test file cannot be downloaded Pinmemberfredatcodeproject29-Jan-14 22:13 
QuestionNice intro to auto-generating testcases PinmemberJohn Brett29-Jan-14 1:08 
QuestionReally? Pinmembercjb11028-Jan-14 21:17 
AnswerRe: Really? Pinmemberpipo4428-Jan-14 23:49 
GeneralRe: Really? PinmemberJohn Brett29-Jan-14 1:02 
GeneralRe: Really? PinmemberFabricePA29-Jan-14 6:19 
Questionimage can not display PinmemberSouthmountain28-Jan-14 12:45 
AnswerRe: image can not display Pinmemberpipo4428-Jan-14 23:39 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    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 | Mobile
Web03 | 2.8.140721.1 | Last Updated 31 Mar 2014
Article Copyright 2014 by G.TR
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid