Writing your first tests

Writing tests for your code isn’t difficult. It’s simply a matter of fixtures, tests and asserts.

  • An assert is a simple check. For example, that an integer is less than another, that a variable is of a given type, or that a value in a database is the expected one. The idea of asserts in .NET is not a new one, but in unit testing, an assert is the most basic ingredient of the development process. MbUnit pre-defines a lot of different asserts so you don't have to. It also leaves you free to build your own as you need to.
  • A test is a collection of asserts intended to prove that an action or series of actions in your production code actually does what you think it does. For example, that your SaveToDB() method has actually saved information to your database correctly, that your Add() method really does add those values together or that the default constructor for your class doesn't create a null object.
  • A fixture is a collection of tests usually related to a single class in your production code. It doesn’t have to be, but that’s usually how it turns out.

With that knowledge in your head, let’s write a few tests for an application to help you win at the classic fizzbuzz game. The rules are easy.

  • If a number is wholly divisible by 3, return the word fizz.
  • If a number is wholly divisible by 5, return the word buzz.
  • If a number is wholly divisible by 3 and 5, return the word fizzbuzz.
  • If a number isn't wholly divisible by either or 5, return the number.

To keep things simple, we’ll just write the first few tests for a simple static method called ToFizzBuzz() that takes an integer as an argument and returns the correct fizzbuzz string.

Setting Up The Solution

There are two options when it comes to organising your test code in your solution

  • You can write your tests in the same project as the code you are testing, as demonstrated over on our main site.
  • You can keep your tests in a separate assembly from the code you are testing, which we'll demonstrate here. It takes a bit longer to set up but does mean your test code won't be included in any live code you release.

First you’ll need to create two projects, one for the production code containing ToFizzBuzz() and one for the test code.

  1. Open up Visual Studio and create a new c# class library solution. We've called it FizzBuzz and renamed class1.cs to fizzbuzz.cs .
  2. Once it has been created, add another c# class library project to the solution called FizzbuzzTests and renamed class1.cs to fizzbuzztests.cs.
  3. Add a reference to MbUnit.Framework.dll to the FizzBuzzTests project. You'll find it in the .NET tab.

    Adding MbUnit as a reference in VS

  4. You'll also need to add a reference to the FizzBuzz project to FizzBuzzTests.
  5. Solution explorer should now look something like this

    Your solution setup ready for writing tests

That's you all set up. Now all you need to do are write tests and some code.

Writing the Tests

With the project setup, writing the tests is straightforward.

  • First, add a couple of using statements to the top of the test class; one for MbUnit.Framework so our tests will compile and one for the FizzBuzz class we're testing
  • Next, you need to define a test fixture. Recall from above that a fixture is a collection of tests defined in a class. To do that, we decorate the FizzBuzzTests class with the [TestFixture] attribute. Easy. Your code should now look like this.

    Copy 
    1: using System;
    Copy 
    2: using FizzBuzz;
    Copy 
    3: using MbUnit.Framework;
    Copy 
    4:
    Copy 
    5: namespace FizzBuzzTests
    Copy 
    6: {
    Copy 
    7:    [TestFixture]
    Copy 
    8:    public class FizzBuzzTests
    Copy 
    9:    {
    Copy 
    10:   }
    Copy 
    11: }

  • Tests should always be written before writing code. So we start by making sure that ToFizzBuzz() will return “1” when given the number 1 with the following test. This exerc

    Copy 
    10: [Test]
    Copy 
    11: public void ToFizzBuzz_Send1_Returns1()
    Copy 
    12: {
    Copy 
    13:    Assert.AreEqual("1", FizzBuzz.FizzBuzz.ToFizzBuzz(1));
    Copy 
    14: }

    As you can see, we have one assert in this test: that ToFizzBuzz() returns "1" when we send it the integer 1.

  • Now we run the test to make sure that it fails. We can do this with either the console test runner, the GUI runner, or another third party test runner such as TestDriven.NET or Resharper. The choice is yours. Follow the links to see how to run the tests.
  • Now to write the simplest code that satisfies the test. Thus we have.

    Copy 
    1: using System;
    Copy 
    2:
    Copy 
    3: namespace FizzBuzz
    Copy 
    4: {
    Copy 
    5:    public class FizzBuzz
    Copy 
    6:    {
    Copy 
    7:       public static string ToFizzBuzz(int number)
    Copy 
    8:       {
    Copy 
    9:          return "1";
    Copy 
    10:       }
    Copy 
    11:    }
    Copy 
    12: }

  • And so the cycle continues. Our next test should be for the number 2. We could write another test called ToFizzBuzz_Send2_Returns2() which basically copies everything in the previous test except for replacing 1s with 2s. However, the key to refactor everywhere if possible applies to our test code, and we can make use of MbUnit's excellent RowTest facility and generalise the test we have for all numbers which aren't divisible by 3 or 5.

    Copy 
    10:       [Row(1)]
    Copy 
    11:       [Row(2)]
    Copy 
    12:       [RowTest]
    Copy 
    13:       public void ToFizzBuzz_SendNumberNotDivisibleBy3Or5_ReturnsNumberAsString(int NumberToTest)
    Copy 
    14:       {
    Copy 
    15:          Assert.AreEqual(NumberToTest.ToString(),
    Copy 
    16:             FizzBuzz.FizzBuzz.ToFizzBuzz(NumberToTest));
    Copy 
    17:       }

    It’s not the catchiest name, but it explains exactly what the test is testing (ToFizzBuzz), how it is being tested (sending it a number which isn't divisible by three or five), and the result it expects (returning the number as a string). The [RowTest] attribute which has replaced [Test] tells MbUnit that this test must be performed using the different values in each row given above [RowTest]. Those values are plugged into the test as parameters - int NumberToTest, in this case - so MbUnit now runs our test twice, once to test ToFizzBuzz() with the number 1 and again with the number 2. If you run the test again, you'll see it succeed once (with the number 1) and fail once (against the number 2).

  • To make both tests succeed, we need to make just a slight alteration to ToFizzBuzz()

    Copy 
    7:       public static string ToFizzBuzz(int number)
    Copy 
    8:       {
    Copy 
    9:          return number.ToString();
    Copy 
    10:       }

  • And so we continue the cycle of writing tests and then writing the code that satisfies them. There are three more tests to write for ToFizzBuzz.
    • Sending it a multiple of 3 but not of 15 and it returning "fizz"
    • Sending it a multiple of 5 but not of 15 and it returning "buzz"
    • Sending it a multiple of 15 and it returning "fizzbuzz"
    The first might look something like the following.

    Copy 
    19:       [Row(3)]
    Copy 
    20:       [Row(6)]
    Copy 
    21:       [RowTest]
    Copy 
    22:       public void ToFizzBuzz_SendNumberDivisibleBy3ButNot5_ReturnsFizz(int NumberToTest)
    Copy 
    23:       {
    Copy 
    24:          Assert.AreEqual("fizz", FizzBuzz.FizzBuzz.ToFizzBuzz(NumberToTest));
    Copy 
    25:       }

And so on. The point here is not how to write the best implementation of ToFizzBuzz() - there are several equally good ones - but that writing tests is not any different from writing another piece of code. The key is that your tests must be correct before your code can be. If we added [Row(15)] to this latest test, it would expect ToFizzBuzz() to return "fizz" rather than "fizzbuzz" as it should.

What MbUnit provides you with are methods such as the row test to let you perform these tests with the minimum of coding required. To that extent, the MbUnit.Framework library offers a great number of asserts for checking values, arrays, collections, data, files and more for you to use and a number of different test types in addition to the vanilla [Test] and strawberry flavoured [RowTest] you've seen here. It also lets you specify a test’s author or category for review later on. Perhaps you'll want to stop a few tests running for a while or flag issues with a test during a run? You can do that too with ignore flags and warnings.

Everything mentioned here and a lot more is covered in our API reference in detail. If you're not sure what to start, perhaps you should check out this rough guide.

More Step by Step guides

Check out our articles page for more step by step guides to writing code using tests.