Test Driven Development in .NET Part 2: continuing with Red, Green, Refactor

In the previous post we looked at the very basics of test first development in .NET and more specifically the Red-Green-Refactor cycle. This post is a direct continuation so we’ll build on the test project we started working on.

Pass the test

Currently we have a failing test in our test project: the SharesTest() fails. Remove the ‘throw new NotImplementedException’ from the Partition method and replace it with the simplest possible return statement that will make the compiler happy:

return new Partition();

Run the tests now and we should still have a failing test: the Size property was initialised to 0 which is different from the expected value of one:

Shares test still fails

It’s now time to make sure that our test passes. The simplest way is probably the following:

public Partition Partition(List<Share> sharesList)
        {
            return new Partition() { Size = sharesList.Count };
        }

Run the test now and you should see that SharesTest indeed passes:

Shares test passed

We assign the Count property of the sharesList parameter to the Size property of the Partition object. As we passed in a list with a single share then the result will be 1 which is the expected result. This is of course not a real life implementation yet. The Partition method doesn’t even look at the integer in the Partitioner constructor. That’s OK as we’ll now move on to the next step in the cycle: Refactoring.

The code generator called the integer parameter of the Partition constructor ‘p’. That’s not very descriptive so change the code as follows:

        private int _partitionSize;

        public Partitioner(int partitionSize)
        {
            this._partitionSize = partitionSize;
        }

Re-run the test to make sure it’s working. This is a good exercise: you change something in the implementation and then run the tests to check whether you broke something.

We now know a bit more about the purpose of SharesTest: it tests whether a group of size one is partitioned into a Partition of size one. There are different ways to name a test but a descriptive approach can be worthwhile. Rename the SharesTest method to Partitioning_a_list_of_one_item_by_one_produces_a_partition_of_size_one().

Run the test and you’ll see the new method name appear in the Test Explorer. As the name describes what the test does it’s easy to see which functions pass or fail. SharesTest doesn’t tell you anything: what Shares? What functionality? It passes, but what is it that passes? Choosing a long name like that saves you a lot of time: the title tells you which functionality is broken.

We can again stop for some reflection: is it enough to test a single case? Should we check other partition values such as -1, 0, 2, 100 etc.? Testing the value of 1 is probably not enough as the users may pass in values that are outside of your control.

You may be tempted to add several assertions into one test but resist that: you should only have a single assertion within one test. We should always have one single assertion per unit test. We test one scenario and not more. Then if the test fails then you’ll immediately see which functionality is failing.

Insert a new test in FinanceTests, a test that checks if a collection of 2 items returns a Partition of size 2:

        [Test]
        public void Partitioning_a_list_of_two_items_by_one_produces_a_partition_of_size_two()
        {
            List<Share> sharesList = new List<Share>();
            Share shareOne = new Share();
            shareOne.Maximum = 100;
            shareOne.Minimum = 13;
            sharesList.Add(shareOne);
            sharesList.Add(new Share() { Maximum = 50, Minimum = 10 });            

            Partitioner partitioner = new Partitioner(1);
            Partition partition = partitioner.Partition(sharesList);

            Assert.AreEqual(2, partition.Size);
        }

We now have a collection of 2 shares. The collection is partitioned into groups of one and we expect the resulting Partition two have two elements. Run the tests and you’ll see that it passes. It’s obvious: our implementation of the Partition method still doesn’t even look at the _partitionSize property so it doesn’t make any difference whether we pass in a collection of 2, 5 or 100. So it’s now time to come up with something more realistic.

Add another property to the Partition object:

public IList<IList<Share>> PartitioningResult;

The result of the partitioning process should be a list of lists shares. If we start with a list of 10 shares which should be cut into two subgroups of 5 then we’ll end up with a list of lists where the individual subgroups have a size of 5. The Partition method might look like this:

public Partition Partition(List<Share> sharesList)
        {
            IList<IList<Share>> partitioningResult = new List<IList<Share>>();
            int total = 0;
            while (total < sharesList.Count)
            {
                List<Share> subGroup = sharesList.Skip(total).Take(_partitionSize).ToList();
                partitioningResult.Add(subGroup);
                total += _partitionSize;
            }

            return new Partition() { PartitioningResult = partitioningResult, Size = partitioningResult.Count };
        }

Inspect the code and make sure you understand what it is doing. It is straightforward: it chops up the incoming sharesList parameter into subgroups using LINQ and assigns the subgroups to the Partition object along with a new definition of Size. Run the tests and you’ll see that it passes.

The next phase would be to decide what scenarios to test: what if we have a list of 5 shares and want to partition them into groups of two. Should the Partition function throw an exception? Should it return lists of 2-2-1? Or should it drop the element(s) that don’t fit the partition size? These are all questions that the domain expert should be able to answer so that you can write proper tests.

You can see now that a well written test suite will function as a list of specifications. You can of course have the specs listed in a Word document, but honestly, who has the time and energy to read and maintain that? How can you test the specifications in a Word document? If you instead write the tests directly in the Visual Studio editor those will never expire and with meaningful test method names will tell you clearly how the software is supposed to behave.

Test code quality

Test code is also an integral part of the solution so it should also be maintainable. You may think that the test project is less important than production code. The truth is that all important design rules, such as DRY (Don’t Repeat Yourself) still apply here. It needs to be well organised and documented so that you can find your way around when you come back to it to make changes.

As you add more and more test cases in our Finance test project you may be tempted to copy and paste the original Partitioning_a_list_of_one_item_by_one_produces_a_partition_of_size_one method, rename it and replace the parameters that are required Partitioner, Partition and Assert. Why would you copy and paste any bit of code? To save time: it boring to type out the List of shares variable that’s needed in every assertion.

It’s a better idea to go with a helper method:

private List<Share> CreateSharesListOfSize(int size)
        {
            List<Share> shares = new List<Share>();
            for (int i = 0; i < size; i++)
            {
                shares.Add(new Share(){Maximum = 130, Minimum = 15};
            }
            return shares;
        }

The refactored test methods will look like this:

        [Test]
        public void Partitioning_a_list_of_one_item_by_one_produces_a_partition_of_size_one()
        {
            List<Share> sharesList = CreateSharesListOfSize(1);
            Partitioner partitioner = new Partitioner(1);
            Partition partition = partitioner.Partition(sharesList);

            Assert.AreEqual(1, partition.Size);
        }

        [Test]
        public void Partitioning_a_list_of_two_items_by_one_produces_a_partition_of_size_two()
        {
            List<Share> sharesList = CreateSharesListOfSize(2);           

            Partitioner partitioner = new Partitioner(1);
            Partition partition = partitioner.Partition(sharesList);

            Assert.AreEqual(2, partition.Size);
        }

Additional considerations of tests

Besides being maintainable tests should be:

  • Repeatable
  • Independent
  • Test only public members
  • Atomic
  • Deterministic
  • Fast

A repeatable test means that if a test fails then it should always fail. We can’t say that a test fails between 10am and 5pm, otherwise there’s some external Date function that the test has no control of. Make sure that all those dependencies are controlled by the test method to avoid any such constraints.

Independent tests are tests that can be run in any order without affecting the pass/fail result. There should be no specification saying that test A must be run before test B for it to give the correct result. Don’t make a test dependent on the state left over by another test. Every test should have all necessary dependencies at their disposal and should start with a clean slate.

Testing public members only puts us in the shoes of the client, i.e. the consumer of the API. While writing the initial tests you are forced to think through the design of the API: what objects and methods are needed, what should we call them, what parameters should they take etc. A client is ultimately interested in the public design of the API not in the private elements which they do not even have visibility of. In addition, by testing public members only we can concentrate on the business rules and leave unimportant internal implementation details alone. An unimportant implementation detail is e.g. the assignment of the private variable in the Partitioner constructor. Do we really need to test if the Partitioner’s private integer field was assigned the value of the incoming parameter? Not really, it’s so trivial and it’s an internal implementation detail.

Atomic means that a unit test tests only one thing at a time, meaning you will have only one Assert statement within the body of the unit test.

A deterministic unit test is one that always provides one affirmative outcome: either pass or fail with 100% certainty, “maybe” and “almost” are not good enough.

You can guess what “fast” means. However, it’s not enough to say “yeah, it’s quite fast”. A good unit test runs VERY fast, we’re talking about 10-50 milliseconds. You should eliminate all factors that slow down the execution of the unit test. Accessing external resources such as web services, databases, physical files make unit test execution slower – and also less reliable as those resources must always be up and running and be in a state that is required by the code under test. We will look at such scenarios on later posts dealing with mocking dependencies.

How to organise the tests

There are certainly many ways to organise a test project. 10 developers may give you 11 different answers, but the following can work for many out there:

  • Make sure to include your tests in a separate .NET project
  • You should have as many test projects as you have ‘normal’ projects. Example: if your solution consists of Application.Web and Application.Domains then you should have two corresponding .NET test projects: Application.Web.Tests and Application.Domains.Tests
  • One level down is the namespace, e.g. Finance. For every namespace you should have a Namespace_Test folder in the correct .NET test projects, Finance_Test in this example
  • Below the namespace we have the Class, e.g. Share. For each class you should have a Class_Test folder, Share_Test in this example
  • Within the Share_Test folder we’ll have our test class which tests the behaviour of the Share object
  • Behaviour means the core business logic and making sure that unimportant internal implementation details are not tested. Those tests are not worth writing. E.g. testing a getter and setter is futile unless they incorporate important business logic, such as refusing certain values

So our little .NET solution might look like this after some renaming:

Proposed test class organisation

You may be asking why the test suite of the Partitioner class has such a strange name, When_partitioning_shares.cs. Besides the fact that it is what we test, i.e. partition a list of shares, check how the test class name and the individual test cases can be read in the Test explorer:

Naming the test class

When partitioning shares, partitioning a list of two items by one produces a partition of size two. This sentence gives you the scenario and the expected outcome.

Keep test code DRY

The DRY, i.e. don’t repeat yourself principle applies to the test code as well. There will be parts in the code that all the test methods will need. In our example a list of shares is created in Partitioning_a_list_of_one_item_by_one_produces_a_partition_of_size_one() and Partitioning_a_list_of_two_items_by_one_produces_a_partition_of_size_two(). Remove these two methods and instead add the following two:

[Test]
        public void Partitioning_a_list_of_four_items_by_one_produces_a_partition_of_size_four()
        {
            List<Share> sharesList = CreateSharesListOfSize(4);

            Partitioner partitioner = new Partitioner(1);
            Partition partition = partitioner.Partition(sharesList);

            Assert.AreEqual(4, partition.Size);
        }

        [Test]
        public void Partitioning_a_list_of_four_items_by_four_produces_a_partition_of_size_one()
        {
            List<Share> sharesList = CreateSharesListOfSize(4);

            Partitioner partitioner = new Partitioner(4);
            Partition partition = partitioner.Partition(sharesList);

            Assert.AreEqual(1, partition.Size);
        }

        [Test]
        public void Partitioning_a_list_of_four_items_by_two_produces_a_partition_of_size_two()
        {
            List<Share> sharesList = CreateSharesListOfSize(4);

            Partitioner partitioner = new Partitioner(2);
            Partition partition = partitioner.Partition(sharesList);

            Assert.AreEqual(2, partition.Size);
        }

i.e. we run two tests on a list of 4 shares in all 3 cases. The code that builds the the list is repeated in every test method. As it turns out NUnit – and in fact all major test frameworks out there – makes it easy to run a piece of code before every test method is run in the test suite. This “pre-test” code must be decorated with the [SetUp] attribute. Update the test suite to the following:

[TestFixture]
    public class When_partitioning_shares
    {
        List<Share> _sharesList;

        [SetUp]
        public void SetupTest()
        {
            _sharesList = CreateSharesListOfSize(4);
        }

        [Test]
        public void Partitioning_a_list_of_four_items_by_one_produces_a_partition_of_size_four()
        {
            Partitioner partitioner = new Partitioner(1);
            Partition partition = partitioner.Partition(_sharesList);

            Assert.AreEqual(4, partition.Size);
        }

        [Test]
        public void Partitioning_a_list_of_four_items_by_four_produces_a_partition_of_size_one()
        {
            Partitioner partitioner = new Partitioner(4);
            Partition partition = partitioner.Partition(_sharesList);

            Assert.AreEqual(1, partition.Size);
        }

        [Test]
        public void Partitioning_a_list_of_four_items_by_two_produces_a_partition_of_size_two()
        {
            Partitioner partitioner = new Partitioner(2);
            Partition partition = partitioner.Partition(_sharesList);

            Assert.AreEqual(2, partition.Size);
        }

        private List<Share> CreateSharesListOfSize(int size)
        {
            List<Share> shares = new List<Share>();
            for (int i = 0; i < size; i++)
            {
                shares.Add(new Share(){Maximum = 130, Minimum = 15});
            }
            return shares;
        }
    }

The SetupTest method will be run before every other test method in the file. It simply assigns a value of four shares to the private _sharesList variable.

Conversely if you decorate your method with the TearDown attribute it will be run AFTER each test method execution. The TearDown method can be used to reset some state to an initial value or clean up resources.

It’s quite tedious to create all these test cases, right? It would be best to create one test method and somehow run the same method using many different input parameters without having to copy-paste the existing code. It is possible using the TestFixture attribute. How it is done will be the topic of the next post – amongst several other features.

Test Driven Development in .NET Part 1: the absolute basics of Red, Green, Refactor

In this series of posts we’ll look at ways of introducing Test Driven Development in a .NET project. I’ll assume that you know the benefits of TDD in general and rather wish to proceed with possible implementations in .NET.

The test project

Open Visual Studio 2012 and create a Blank Solution. Right click the solution and select Add… New Project. Add a new C# class library called Application.Domain. You can safely remove the automatically inserted Class1.cs file. You should have a starting point similar to the following:

TDD project starting point Visual Studio

This Domain project represents the business logic we want to test.

Add another C# class library to the application and call it Application.Domain.Tests. Delete Class1.cs. As we want to test the domain logic we need to add a reference to the Application.Domain project to the Tests project.

Also, we’ll need to include a testing framework in our solution. Our framework of choice is the very popular NUnit. Right-click References in Application.Domain.Tests and select Manage NuGet Packages. Search for ‘nunit’ and then install two following two packages:

NUnit projects in NuGet

The NUnit.Runner will be our test runner, i.e. the programme that runs the tests in the Tests project.

You should end up with the below structure in Visual Studio:

VS solution with NUnit installed

We are now ready to add the first test to our project.

A test is nothing else but a normal C# class with some specific attributes. These attributes declare that a class is used for testing or that a method is a test method that needs to run when we test our logic. Every testing framework will have these attributes and they can be very different but serve the same purpose. In NUnit a test class is declared with the TextFixture attribute and a test method is decorated with the Test attribute. These attributes help the test runner identify where to look for tests. It won’t just run random methods, we need to tell it where to look.

This means that it is perfectly acceptable to have e.g. helper methods within the Tests project. The test runner will not run a method that is not decorated with the Test attribute. You can have as many test methods within a Test project as you wish.

The test framework will also have a special set of keywords dedicated to assertions. After all we want our test methods to tell us whether the test has passed or not. Example: we expect our calculator to return 2 when testing for ‘1+1’ and we can instruct the test method to assert that this is the case. This assertion will then pass or fail and we’ll see the result in the test runner window.

Add a new class to Tests called DomainTestFixture and decorate it with the TestFixture attribute:

[TestFixture]
    public class DomainTestFixture
    {
    }

You will be asked to add a using statement to reference the NUnit.Framework namespace.

A test method is one which doesn’t take any parameters and doesn’t return any values. Add the first test to the test class:

[TestFixture]
    public class DomainTestFixture
    {
        [Test]
        public void FirstTest()
        {

        }
    }

To introduce an assertion use the Assert object. Type ‘Assert’ within FirstTest followed by a period. IntelliSense will show a whole range of possible assertions: AreEqual, AreNotEqual, Greater, GreaterOrEqual etc. Inspect the available assertions using IntelliSense as you wish. Let’s test a simple math problem as follows:

[Test]
        public void FirstTest()
        {
            int result = 10 - 5;
            Assert.AreEqual(4, result);
        }

…where ‘4’ is the expected value of the operation and ‘result’ is the result by some operation. Imagine that ‘result’ comes from a Calculator application and we want to test its subtraction function by passing in 10 and 5. Let’s say that we make a mistake and expect 10 – 5 to be 4. This test should obviously fail.

In order to run the test in the NUnit test runner go to Tools, Extensions and Updates. Click ‘Online’ and the search for NUnit. Install the following package:

NUnit test adapter in Visual Studio

You’ll need to restart Visual Studio for the changes to take effect. Then go to Test, Run, All Tests (Ctrl R, A) which will compile the project and run the NUnit tests. You will receive the outcome in the Test Explorer window:

NUnit test explorer first test

As expected, our test failed miserably. You’ll see that the expected value was 4 but the actual outcome was 5. You’ll also receive some metadata: where the test occurred – FirstTest -, the source – DomainTestFixture.cs and the stacktrace.

Go back and fix the assertion:

Assert.AreEqual(5, result);

Select Run All in the Test Explorer and you’ll see that the red turned green and our test has passed. We can move on to a more realistic scenario and we will follow a test-first approach: we’ll write a test for a bit of code that does not even exist yet. The code to be tested will be generated while writing the test.

Let’s add a new class in Tests called FinanceTests.cs. We’ll pretend that we’re working on a financial application that administers shares. It happens often that you’re not sure what to call your test classes and test methods but don’t worry about them too much. Those are only names that can be changed very easily. Let’s add our first Test:

[TestFixture]
    public class FinanceTests
    {
        [Test]
        public void SharesTest()
        {

        }
    }

You’ll see that SharesTest sounds extremely general but remember: in the beginning we may not even know exactly what our Domain looks like. We’ll now test the behaviour of a collection of shares. Add the following bit of code to SharesTest:

List<Share> sharesList = new List<Share>();

This won’t compile obviously at first but we can use Visual Studio to create the object for us. Place the cursor on ‘Share’ and press Ctrl +’.’. You’ll see that a small menu pops up underneath ‘Share’. You can select between Generate class and Generate new type. Select Generate new type. Inspect the possible values in each drop-down menu in the Generate New Type window, they should be self-explanatory. Select the following values and press OK:

Generate new type in VS

You’ll see that a file called Share.cs was created in the Domain project. Next add the following to SharesList:

Share shareOne = new Share();
shareOne.Maximum = 100;
shareOne.Minimum = 13;
sharesList.Add(shareOne);

Again, the code won’t compile first. You can follow the same procedure as with the Share class: place the cursor on ‘Maximum’ and press Ctrl + ‘.’. Select ‘Generate property stub’. Go to Share.cs and you’ll see that an Integer property called Maximum has been added. Do the same with ‘Minimum’. At this point your Share class should look like this:

public class Share
    {
        public int Maximum { get; set; }
        public int Minimum { get; set; }
    }

You’ll notice that at this point we only added a single Share to our shares list. That’s OK, we’ll start with the simplest possible case. This is always a good idea in TDD: always start with the simplest case which is easy to test and easy to write an assertion for. Example: if you want to test a Calculator you probably won’t start with e + Pi as the first test case but something simpler such as 2 + 3. When your test is complete for the simple cases then you can move on to the more difficult ones.

Next we would like to do something with this Shares collection. Let’s imagine that we’re writing code to group the elements in the collection in some way. So we may write the following code in SharesTest():

Partitioner partitioner = new Partitioner(1);
            var partition = partitioner.Partition(sharesList);

This is the time to reflect: what name should we give to the class that will group the list elements? What should the method be called? What type of value should it return? I’m a not great fan of the ‘var’ keyword but in this case it comes in handy as I’m not sure what type of object the Partition method should return. The integer we pass in the Partitioner constructor means that we want to group elements by one. Again, we should stop and reflect: does it make sense to allow users to group items by one? Can they pass in 0 or negative values? Or even int.Max? Should we throw an exception then? These are all rules that you will need to consider, possibly with the product owner or the domain expert.

If we allow users to group the items by one then we should probably test for it. Add the following assertion:

Assert.AreEqual(1, partition.Size);

…meaning that if we instruct the Partitioner to create groups of one then the size of the resulting partition should be 1. Now I have also decided that the Partition() method should return a… …Partition! Update the relevant line as follows:

Partition partition = partitioner.Partition(sharesList);

Using the technique we used before create the Partitioner and Partition classes, the Partition method stub and the Size property stub. Don’t worry about the implementations yet. Make sure that you select the Domain project when creating the classes. The Partitioner class should look as follows:

public class Partitioner
    {
        private int p;

        public Partitioner(int p)
        {
            // TODO: Complete member initialization
            this.p = p;
        }

        public Partition Partition(List<Share> sharesList)
        {
            throw new NotImplementedException();
        }
    }

Partition.cs:

public class Partition
    {
        public object Size { get; set; }
    }

Overwrite the type of the Size property to ‘int’.

At this point the projects should compile just fine. Run the test by pressing Ctrl R, A and see what happens. You will of course see that our SharesTest has failed:

First shares test failure in visual studio

We have not implemented the Partition method yet, so we obviously cannot have a passing test.

This is exactly the first thing that we wanted to happen: a failing test.

The Red – Green – Refactor cycle

The Red – Green – Refactor cycle is a fundamental one in TDD. We’re at the Red stage at present as we have a failing test which correspond to Step 1: Create a failing test. You may wonder why this is necessary: a failing test makes sure that our method under test is testable. It is important to see that it can fail. If a method never ever can fail then it is not testable. Therefore make sure that you follow this first step in your test creation. The first step involves other important things we have to consider: name of the classes, name of methods, parameter types, return types, business rules etc. These are all very important considerations that you need to take into during this first step.

Step 2, i.e. Green involves a minimalistic implementation of our method stub(s): write just enough to make the test pass, i.e. replace the red light with green. Do not write the complete implementation of the method just yet, that will happen in different stages.

Step 3 is Refactoring, which is the gradual implementation of the method under test. This is a gradual process where you extend the implementation of the method without changing its external behaviour, i.e. the signature, and run the test over and over again to make sure that the method still fulfills the assertion. Did the change break the tests? Or do the tests still pass? You can come back to your code a year later and still have the tests in place. They will tell you immediately if you’ve broken something.

You may think that all this is only some kind of funny game to produce extremely simple code. We all know that real life code is a lot more complicated: ask a database, run a file search, contact a web service etc. How can those be tested? Is TDD only meant for the easy stuff in memory? No, TDD can be used to test virtually anything – as long as the code is testable. If you follow test first development then testability is more or less guaranteed. There are ways to remove those external dependencies such as Services, Repositories, web service calls etc. and test the REAL purpose of the method. The real purpose of a method is rarely just to open a file – it probably needs to read some data and analyse it.

If, however, you write lengthy implementations at first and then write the tests then testability is at risk. It’s easy to fall into traps that make testability difficult: dependencies, multiple tasks within a method – violating the Single Responsibility Principle, side effects etc. can all inadvertently creep in.

We’ll stop here at the Red phase of the TDD cycle – the next post will look the Green and Refactor.

ultimatemindsettoday

A great WordPress.com site

Elliot Balynn's Blog

A directory of wonderful thoughts

HarsH ReaLiTy

A Good Blog is Hard to Find

Softwarearchitektur in der Praxis

Wissenswertes zu Webentwicklung, Domain-Driven Design und Microservices

Technology Talks

on Microsoft technologies, Web, Android and others

Software Engineering

Web development

Disparate Opinions

Various tidbits

chsakell's Blog

WEB APPLICATION DEVELOPMENT TUTORIALS WITH OPEN-SOURCE PROJECTS

Guru N Guns's

OneSolution To dOTnET.

Johnny Zraiby

Measuring programming progress by lines of code is like measuring aircraft building progress by weight.

%d bloggers like this: