Friday, July 14, 2023

Test-Driven Development Tutorial

 Many years ago when I first learned how to do test-driven development I used a guide I found on C# Corner and I really liked it. Over the years I've referred people to that same guide, but when I checked it out again recently I realized it has never been updated so it still refers to Visual Studio features that are no longer available, and uses NUnit instead of XUnit. I figured I'd give it a makeover and post it here, but you should definitely credit Dom Millar for this one. Everything after this point is lifted straight from that post, with the only changes being those for Visual Studio versions, NUnit -> XUnit, and maybe some syntax.


Introduction - Defining the Battlefield

This tutorial is a short introduction to using Test Driven Development (TDD) in Visual Studio 2022 (VS2022) with C#. Like most of my examples it's based on a game.

By completing this tutorial you will:

  • Get a taste of TDD through a series of small iterations
  • Learn how VS2022 provides TDD support through a number of features
  • Learn a number of C# features

CannonAttack is a simple text based game in which a player enters an angle and velocity of a cannonball to hit a target at a given distance. The game uses a basic formula for calculating trajectory of the cannonball and the player keeps taking turns shooting at the target until it has been hit. I won't go into TDD theory in any great detail now, but you should check out the number of great references to TDD including:

http://en.wikipedia.org/wiki/Test_driven_development

http://www.codeproject.com/KB/dotnet/tdd_in_dotnet.aspx

C# .NET 2.0 Test Driven Development by Matthew Cochran

http://msdn.microsoft.com/en-us/library/dd998313(VS.100).aspx

The following are the fundamental steps of a TDD iteration:

  • RED - take a piece of functionality and build a test for it and make it fail the test by writing a minimum amount of code (basically just get it to compile and run the test)
  • GREEN - write minimal code for the test to make it succeed
  • REFACTOR - clean up and reorganize the code and ensure it passes the test and any previous tests
     

In this tutorial we will be progressing through a number of iterations of the TDD cycle to produce a fully functional simple application. Each iteration will pick up one or more of the requirements/specs from our list (see The CannonAttack Requirements/Specs below). We won't test for absolutely everything and some of the tests are fairly basic and simplistic, but I am just trying to keep things reasonably simple at this stage

VS2022 and C# 4.0:

This tutorial covers the use of VS2022 and targets a number of features of C# 4.0 [editor's note: the current version of C# is 11.0, but this guide goes out of its way to introduce features of C# 4.0 so I left all references to C# 4.0 intact],

  • Generating stubs for TDD in VS2022
  • Test Impact View in VS2022 [editor's note: this feature disappeared sometime between VS2010 and VS2022 and the closest feature I've been able to find is Live Unit Testing, which is only available in Enterprise Edition]
  • Tuples
  • String.IsNullOrWhiteSpace method

There are many more features of C# 4.0 and we will be covering them in future tutorials.

What you need:

  • This is a C# tutorial so a background in C# would be very useful
  • VS2022

The CannonAttack Requirements/Specs:

The following is a combination of Requirements and Specifications that will give us some guide in terms of the application we are trying to build: 

  • Windows Console Application
  • Player identified by an id, default is set to a constant "Human"
  • Single player only, no multi-play yet
  • Allow player to set Angle and Speed of the Cannon Ball to Shoot at a Target
  • Target Distance is simply the distance of the Cannon to Target, and is created randomly by default but can be overridden
  • Angle and Speed needs to be validated (specifically not greater than 90 degrees and Speed not greater than speed of light)
  • Max distance for target is 20000 meters
  • Base the algorithm for the calculation of the cannon's trajectory upon the following C# code (distance and height is meters and velocity is meters per second):
    • distance = velocity * Math.Cos(angleInRadians) * time;
    • height = (velocity * Math.Sin(angleInRadians) * time) - (GRAVITY * Math.Pow(time, 2)) / 2;
  • A hit occurs if the cannon is within 50m of the target
  • Game text will be similar to the following:
    Welcome to Cannon Attack
    Target Distance: 12621m
    Please Enter Angle: 40
    Please Enter Speed: 350
    Missed cannonball landed at 12333m
    Please Enter Angle: 45
    Please Enter Speed: 350
    Hit - 2 Shots
    Would you like to play again (Y/N)
    Y
    Target Distance: 2078m
    Please Enter Angle: 45
    Please Enter Speed: 100
    Missed cannonball landed at 1060m
    Please Enter Angle: 45
    Please Enter Speed: 170
    Missed cannonball landed at 3005m
    Please Enter Angle: 45
    Please Enter Speed: 140
    Hit - 3 shots
    Would you like to play again (Y/N)
    N
    Thank you for playing CannonAttack

OK so now we are ready to code, let's go...

Iteration 1 - Creating the Cannon

Steps

  • Start Visual Studio
  • Click Create a new project
  • From the list of available project types, select Console App (be sure to select the C# version and not the VB version) and click Next
  • Call the application CannonAttack and click Next
  • Select .NET 7.0 (Standard Term Support) from the Framework dropdown and click Create
  • When the solution has loaded right click on the solution in Solution Explorer and select ADD -> NEW PROJECT
    • If you can't see the Solution Explorer expand the View menu and choose Solution Explorer
  • Select xUnit Test Project and click Next
  • Call the test project CannonAttackTest and click Next
  • Select .NET 7.0 (Standard Term Support) from the Framework dropdown and click Create
  • Rename UnitTest1.cs to CannonAttackTest.cs
  • If you see the following select YES
  • You should see the Solution Explorer as:
  • Open the code window for the CannonAttackTest.cs file and you should see the default test method
    • The [Fact] attribute above a method in this file indicates VS2022 will add this method as a unit test
  • Rename the default test method to CannonShouldHaveAValidId
  • Add the code var cannon = new Cannon(); to the body of the unit test method
  • Your updated unit test should look like this:
[Fact]
public void CannonShouldHaveAValidId()
{
    var cannon = new Cannon();
}
  • You'll notice an error because the type Cannon does not exist yet
  • Place your insertion point on Cannon and use the CTRL+. keyboard shortcut to get the Intellisense menu to display
  • Choose Generate new type... from the Intellisense menu
  • Select CannonAttack from the Project dropdown and choose the Create new file radio button
    • Leave the default new file name as Cannon.cs
  • At the top of the test file, make sure to add a using statement for the CannonAttack namespace
  • Update the test method to this:
[Fact]
public void CannonShouldHaveAValidId()
{
    var cannon = new Cannon();
 
    Assert.NotNull(cannon.Id);
}
  • Once again the solution will not compile because the Cannon type does not have an Id property
  • Use the CTRL+. keyboard shortcut to get the Intellisense menu to display
  • This time choose Generate property 'Id' from the Intellisene menu
    • This creates an auto property which will meet our needs for now
  • The solution should compile at this point
  • Save all projects and run all tests by using the Test Explorer
    • If you can't see the Test Explorer expand the Test menu and choose Test Explorer
    • To run all tests, click the button with a solid green arrow on top of a green arrow outline
  • The test failed, which is correct and expected as the first phase of TDD is RED - failed!!!
  • Now that we have a red test, let's get this test to pass
  • Select Cannon.cs in the CannonAttack project and make the following change to the Id property
public string Id
{
    get
    {
        return "Human";
    }
}
  • Run all tests again by using the Test Explorer
    • You can also repeat the last test by using the keyboard shortcut CTRL+R, l
  • All tests should now be passing and your Test Explorer should look similar to this:
  • We have just completed the second stage of a TDD cycle, GREEN - Pass!!!
[editor's note: I've added the next few steps so that unit tests are written to verify the default value is used unless a value is provided]
  • Add two more unit tests called CannonShouldUseDefaultValueForIdWhenNoValueIsProvided and CannonShouldUseProvidedValueForIdWhenAValueIsProvided that looks like this:
[Fact]
public void CannonShouldUseDefaultValueForIdWhenNoValueIsProvided()
{
    var cannon = new Cannon();
 
    Assert.Equal("Human", cannon.Id);
}
[Fact]
public void CannonShouldUseProvidedValueForIdWhenAValueIsProvided()
{
    var cannon = new Cannon
    {
        Id = "Test Value"
    };
 
    Assert.Equal("Test Value", cannon.Id);
}
  • The second test won't compile right now, and the first test should pass
  • Modify the Cannon class to look like this so the code compiles and the third test fails:
namespace CannonAttack
{
    public class Cannon
    {
        public Cannon()
        {
        }
 
        public string Id
        {
            get
            {
                return "Human";
            }
            set { }
        }
    }
}
  • We now have three tests, two that pass and one that fails; it is time to refactor our code
  • Make the following changes to the Cannon class so that all three tests pass:
namespace CannonAttack
{
    public sealed class Cannon
    {
        private readonly string CANNONID = "Human";
        private string CannonId;
 
        public string Id
        {
            get
            {
                return string.IsNullOrWhiteSpace(CannonId) ? CANNONID : CannonId;
            }
            set
            {
                CannonId = value;
            }
        }
    }
}

We have made this class sealed so that it is not inherited by anything. Also, we have added a readonly string to store a default ID if not set by the user. I am going to use runtime constants (readonly) because they are more flexible than compile time constants (const) and if you are interested check out Bill Wagner's book (effective C# - 50 Ways to Improve your C#) for further details.

Let's run the tests again. Again they should compile and pass because although we have made some changes to the code, we should not have impacted the tests and this is an important part of the Refactor phase. We should make the changes we need to make the code more efficient and reusable, but it is critical that the same tests that we made pass in the Green phase still pass in the Refactor phase.

The refactoring is complete. Now for ITERATION 2 of the CannonAttack project.

Iteration 2 - One Cannon, and only one Cannon - Using the Singleton Pattern.

Like the previous iteration we will pick an element of functionality and work through the same sequence again RED->GREEN->REFACTOR. Our next requirement is to allow only a single player. Given that we can allow 1 player we really should only use one instance, let's create a test for only one instance of the cannon object. We can compare two objects to ensure they are pointing at the same instance like (obj == obj2).

  • Add a new test beneath the three we already have in CannonAttackTest.cs that looks like this:
[Fact]
public void CannonCannotBeCreatedMoreThanOnce()
{
    var cannon = new Cannon();
    var cannon2 = new Cannon();
 
    Assert.Equal(cannon, cannon2);
}
  • Run all tests using Test Explorer and you'll see the new test fail, so we are at stage 1 of TDD again (RED)
  • The reason that this failed is we have created two different instances. What we need is the singleton pattern to solve our problem. I have a great book on patterns called HEAD FIRST DESIGN PATTERNS if you want to know more about design patterns it's a great start - sure its Java but the code is so close to C# you should not have any real problems.
  • We are going to use the singleton pattern to meet the requirement of a single player. Really we don't want multiple instances of cannons hanging around - 1 and only 1 instance is needed. Insert the following Singleton code below the property for the ID.
namespace CannonAttack
{
    public sealed class Cannon
    {
        private readonly string CANNONID = "Human";
        private string CannonId;
        private static Cannon cannonInstance;
 
        private Cannon()
        {
        }
 
        public static  Cannon GetInstance()
        {
            if (cannonInstance == null)
            {
                cannonInstance = new ();
            }
 
            return cannonInstance;
        }
        public string Id
        {
            get
            {
                return string.IsNullOrWhiteSpace(CannonId) ? CANNONID : CannonId;
            }
            set
            {
                CannonId = value;
            }
        }
    }
}
  • If we try to run the tests we won't compile because the cannon object can't be created with Cannon cannon = new Cannon(); So make sure that we use Cannon.GetInstance() instead of new Cannon(). The four test methods should now look like:
[Fact]
public void CannonShouldHaveAValidId()
{
    var cannon = Cannon.GetInstance();
 
    Assert.NotNull(cannon.Id);
}
 
[Fact]
public void CannonShouldUseDefaultValueForIdWhenNoValueIsProvided()
{
    var cannon = Cannon.GetInstance();
 
    Assert.Equal("Human", cannon.Id);
}
 
[Fact]
public void CannonShouldUseProvidedValueForIdWhenAValueIsProvided()
{
    var cannon = Cannon.GetInstance();
    cannon.Id = "Test Value";
 
    Assert.Equal("Test Value", cannon.Id);
}
 
[Fact]
public void CannonCannotBeCreatedMoreThanOnce()
{
    var cannon = Cannon.GetInstance();
    var cannon2 = Cannon.GetInstance();
 
    Assert.Equal(cannon, cannon2);
}
  • Run the tests again

This time they pass GREEN so time to refactor. We are going to change our Singleton code because although the code works (and is pretty much 100% the same as the singleton code in the HEAD FIRST DESIGN PATTERNS Book) it is not thread safe in C# (see http://msdn.microsoft.com/en-us/library/ff650316.aspx) So we replace the original singleton code with:

namespace CannonAttack
{
    public sealed class Cannon
    {
        private readonly string CANNONID = "Human";
        private string CannonId;
        private static Cannon cannonInstance;
        static readonly object padlock = new object();
 
        private Cannon()
        {
        }
 
        public static  Cannon GetInstance()
        {
            lock(padlock)
            {
                if (cannonInstance == null)
                {
                    cannonInstance = new ();
                }
 
                return cannonInstance;
            }
        }
 
        public string Id
        {
            get
            {
                return string.IsNullOrWhiteSpace(CannonId) ? CANNONID : CannonId;
            }
            set
            {
                CannonId = value;
            }
        }
    }
}

The block inside the lock ensures that only one thread enters this block at any given time. Given the importance of determining if there is an instance or not, we should definitely use the lock.

  • Run the tests again and they should all still pass

That's the end of the second iteration and I think you should be getting the hang of it by now, so lets get onto the 3rd iteration.

Iteration 3 - Angling for something...

We will add another couple of test methods. This time we want to ensure that an incorrect angle (say 95 degrees) will not hit. So we need a Shoot method and a return type (lets keep it simple and make it a Boolean for now).

  • Add the following test below the last test:
[Fact]
public void CannonShouldNotFireWithAnAngleTooLarge()
{
    var cannon = Cannon.GetInstance();
 
    Assert.False(cannon.Shoot(95, 100));
}
 
[Fact]
public void CannonShouldNotFireWithAnAngleTooSmall()
{
    var cannon = Cannon.GetInstance();
 
    Assert.False(cannon.Shoot(-1, 100));
}
  • Of course the compiler will complain and we get it to compile by putting the insertion point on Shoot and using the CTRL+. keyboard shortcut to view the Intellisense menu and choosing Generate method 'Shoot'
  • Run all tests from Test Explorer to see that all of the previous tests pass, but the two new tests fail
  • Open Cannon.cs in the CannonAttack project and replace the generated Shoot method with:
public bool Shoot(int angle, int velocity)
{
    if (angle > 90 || angle < 0)
    {
        return false;
    }
 
    return true;
}
  • Run all of the tests again and you'll see they all pass so we're back to the GREEN stage again so it's time to refactor
  • We need to add two extra readonly integers to the class
    • We will add them as public so they are exposed to the console application eventually
public static readonly int MAXANGLE = 90;
public static readonly int MINANGLE = 1;
  • Now refactor the Shoot method to use the new constants
public (bool, string) Shoot(int angle, int velocity)
{
    if (angle > MAXANGLE || angle < MINANGLE)
    {
        return (false, "Angle Incorrect");
    }
 
    return (true, "Angle OK");
}

We have changed the interface of the method so it returns a Tuple indicating if its a hit (BOOL) and also a message (STRING) containing the display text. The Tuple is a feature of C# 4.0. used to group a number of types together and we have used it in conjunction with the type inference of the var to give a neat and quick way to handle the messages from our shoot method. See the following article for further information:http://www.davidhayden.me/2009/12/tuple-in-c-4-c-4-examples.html

  • To handle the change to the Shoot method, we change our two newest tests to:
[Fact]
public void CannonShouldNotFireWithAnAngleTooLarge()
{
    var cannon = Cannon.GetInstance();
 
    var shot = cannon.Shoot(95, 100);
 
    Assert.False(shot.Item1);
}
 
[Fact]
public void CannonShouldNotFireWithAnAngleTooSmall()
{
    var cannon = Cannon.GetInstance();
 
    var shot = cannon.Shoot(0, 100);
 
    Assert.False(shot.Item1);
}

[editor's note: in newer versions of C# you can further refactor this, as shown below]

[Fact]
public void CannonShouldNotFireWithAnAngleTooLarge()
{
    var cannon = Cannon.GetInstance();
 
    var (shotStatus, _) = cannon.Shoot(95, 100);
 
    Assert.False(shotStatus);
}
 
[Fact]
public void CannonShouldNotFireWithAnAngleTooSmall()
{
    var cannon = Cannon.GetInstance();
 
    var (shotStatus, _) = cannon.Shoot(0, 100);
 
    Assert.False(shotStatus);
}

At this point we can refactor our tests, which is an important (though frequently overlooked) part of TDD. Since we're always going to create the Cannon instance the same way, we can move that into the constructor of our CannonAttackTest class so it is automatically run before each test.

The entire CannonAttackTest class now looks as you see below. I'm going to stop here and work on a Part 2 later because this post is long and I have other stuff I need to get done today.

using CannonAttack;
 
namespace CannonAttackTest
{
    public class CannonAttackTest
    {
        private readonly Cannon _cannon;
 
        public CannonAttackTest()
        {
            _cannon = Cannon.GetInstance();
        }
 
        [Fact]
        public void CannonShouldHaveAValidId()
        {
            Assert.NotNull(_cannon.Id);
        }
 
        [Fact]
        public void CannonShouldUseDefaultValueForIdWhenNoValueIsProvided()
        {
            Assert.Equal("Human", _cannon.Id);
        }
 
        [Fact]
        public void CannonShouldUseProvidedValueForIdWhenAValueIsProvided()
        {
            _cannon.Id = "Test Value";
 
            Assert.Equal("Test Value", _cannon.Id);
        }
 
        [Fact]
        public void CannonCannotBeCreatedMoreThanOnce()
        {
            var cannon2 = Cannon.GetInstance();
            Assert.Equal(_cannon, cannon2);
        }
 
        [Fact]
        public void CannonShouldNotFireWithAnAngleTooLarge()
        {
            var (shotStatus, _) = _cannon.Shoot(95, 100);
 
            Assert.False(shotStatus);
        }
 
        [Fact]
        public void CannonShouldNotFireWithAnAngleTooSmall()
        {
            var (shotStatus, _) = _cannon.Shoot(0, 100);
 
            Assert.False(shotStatus);
        }
    }
}

Monday, July 10, 2023

.Net Dependency Injection Options

I was asked today by a colleague about the differences between AddScoped, AddTransient, and AddSingleton. I generally know the differences, but struggled to explain them to her. Fortunately, a quick Google search lead me to a super easy answer.

First, here's the full Stack Overflow answer. There's a TL;DR right at the top that says:

  •  Transient objects are always different; a new instance is provided to every controller and every service
  • Scoped objects are the same within a request, but different across different requests
  • Singleton objects are the same for every object and every request 

That summary comes from this Microsoft tutorial on configuring Dependency Injection.