Getting Started With Unit Testing - VS & C#
I was at a the FlatIron Seattle open house where I met many great people.
I had a great time talking with people about testing and Test Driven Development. It's something not as heavily emphasized as I think it should be in the education and training of individuals in our industry.
A couple of people I was talking to have reached out and want to get started doing testing, but they're facing some challenges getting going. I totally understand this. I spent years trying to do and practice just unit testing... Took me quite a while and until I was working with a team that had done them before, I really never got it.
This is my attempt to demonstrate getting started with unit tests, in C# using Visual Studio on Windows.
I'll be happy to learn and demonstrate other operating systems, tools and languages - just let me know.
The Tools
Since we're on a windows machine, the only installation we need to be sure of is Visual Studio and we can use the community edition just fine.
I HIGHLY recommend getting ReSharper as a extenseion in Visual Studio. I've maintained my own subscription for over a decade even though at least 8 years of that I've had company provided access.
It's not required for unit testing.
Installing Visual Studio we only need the .NET components.
There's some additional options on the right side of the window that we can use to customize, for now - We don't need any additional options.
While it's installing... wait. :) Make sure the "Start after installation" is checked, and we'll be brought right into Visual Studio.
First Project
Your start page in visual studio will look a little different than mine. It's using information from my activities in other instances of Visual Studio.
What we want to start is a Unit Test Project. There's a great "New Project" search box we can use to find a unit test project.
We'll search for Test
and we should get at least one result - I have two, which is likely the same result you'll see.
We'll go with the C# Unit Test Project - Click on it and the New Project dialog will be brought up.
Pick an appropriate name and location to save - or defaults, doesn't really matter. I've went with 'CSharpUnitTestExample' as I'll be putting the code into GitHub when I'm done.
Once you've set all that up, Click OK.
This dumps us into our test class, named UnitTest1
and a test TestMethod1
.
First Test
Now that we're in our test, we'll do some TDD. I like the flow because it has the explicit refactor phase. Red-Green-Refactor. It's a very clear and defined time to change anything.
What problem shall we use.... hmmm... Oh - I know! FizzBuzz
"Write a program that prints the numbers from 1 to 100. But for multiples of three print “Fizz” instead of the number and for the multiples of five print “Buzz”. For numbers which are multiples of both three and five print “FizzBuzz”."
This is part of our code crafter mentality - Practice with something that doesn't require solving so we can have an intentional practice. If you've never done FizzBuzz, go do it a few times. Just explore and solve the problem so that you'll know the solution.
Coming back to this, you'll not need to worry about the problem, but be able to focus on what allows us to test our code.
I follow TDD as described by Uncle Bob and Kent Beck. I like Uncle Bob's three rules for TDD
- You are not allowed to write any production code unless it is to make a failing unit test pass.
- You are not allowed to write any more of a unit test than is sufficient to fail; and compilation failures are failures.
- You are not allowed to write any more production code than is sufficient to pass the one failing unit test.
With this, what'll be the first thing we'll test? I start with numbers that aren't a multiple of three or five - that just return themself.
Should return string 1 given integer 1
That's test we'll write - but first let's look at the code.
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace CSharpUnitTestExample
{
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
}
}
}
The [TestClass]
attribute indicates to the Test Runner that this class has tests. The [TestMethod]
indicates that this method is a test. It needs to be a public void
method with no arguments.
I'm a fan of the Should_Given
style of test naming, but whatever works for now. In bigger projects, a consistent test naming style is important.
I've written the test I want to write in this form already, we'll just translate that to CamelCaseStyle and we have a test name ShouldReturnString1GivenInteger1
.
[TestMethod]
public void ShouldReturnString1GivenInteger1()
{
}
I find a style of writing a test called AAA (Arrage - Act - Assert) to be amazingly effective on helping direct the focus of the test.
The idea is
- Arrange all necessary preconditions and inputs.
- Act on the object or method under test.
- Assert that the expected results have occurred.
[TestMethod]
public void ShouldReturnString1GivenInteger1()
{
//Arrange
//Act
//Assert
}
A simple rule for me is that there can be only a single statement in the Act
section. Ideally we'll only have a single Assert
as well. I concede a bit on this when the entire Arrange
and Act
are identical. Who cares for now, let's get some stuff happening.
What do we need to arrange in our test? Sometimes it's hard to know. I'll often start with the assert to make sure I understand what I am trying to show.
[TestMethod]
public void ShouldReturnString1GivenInteger1()
{
//Arrange
//Act
//Assert
Assert.AreEqual("1", actual);
}
This Assert.AreEqual("1", actual);
says that we need to assert that the expected value of "1" should be equal to the actual value from our Act section... It's kinda an odd way to phrase it, but that's how the default libraries do it.
If you want a more 'readable' form, check out Fluent Assertions. I use it almost all the time. I'l leabving it out here as I want this example to be using minimal amounts of external pieces.
We're not compiling, but it's not yet due to lack of 'prouction' code. We're missing a variable in our test.
If we put the cursor on actual
we can then hot key via CTRL+. and select the presented Generate local 'actual'
to have the IDE write the code for us. The more we use the IDE to do 'mundane' things like typing, the more we can focus on the problem we're trying to solve.
[TestMethod]
public void ShouldReturnString1GivenInteger1()
{
//Arrange
//Act
string actual = null;
//Assert
Assert.AreEqual("1", actual);
}
Now that we understand what we want to test, and have everything compiling, we'll replace teh null
with a call to... some method.
[TestMethod]
public void ShouldReturnString1GivenInteger1()
{
//Arrange
//Act
string actual = FizzBuzzIt(1);
//Assert
Assert.AreEqual("1", actual);
}
Our new method FizzBuzzIt
doesn't exist. A compilation error is a failing test, so let's fix that part.
CTRL+. is going to be our friend. Typically ALT+ENTER when I'm using Resharper.
When our caret is on the failure we'll get the suggestion to have the IDE generate the method - let's do that - and we'll get
private string FizzBuzzIt(int v)
{
throw new NotImplementedException();
}
We now have a failing test!
but... how do I know?
Gotta run them. ... how?
From the menu we can follow Test > Run > All Test
or the hot key CTRL+R,A. As seen here.
When running them, it'll build and execute the tests, and the results will appear in a Test Explorer
If it doesn't, you can open the Window from Test > Windows > Test Explorer
If we look at the failure information at the bottom of the Test Explorer pane we'll see why the test failed.
We will see that the test failed due to
Test method CSharpUnitTestExample.UnitTest1.ShouldReturnString1GivenInteger1 threw exception:
System.NotImplementedException: The method or operation is not implemented.
Which would be OK if that's what we were checking. Our assertion is that the actual value is "1"
. It didn't fail that comparrison, it never got there. Let's get it to fail at that assertion. The reaon is that if the assertion never fails, how can you know why it passed?
I'll return an empty string ""
from the method. That should fail at the assertion.
[TestMethod]
public void ShouldReturnString1GivenInteger1()
{
//Arrange
//Act
string actual = FizzBuzzIt(1);
//Assert
Assert.AreEqual("1", actual);
}
private string FizzBuzzIt(int v)
{
return "";
}
Let's hit 'Run All' again and see what happens...
We fail because
Message: Assert.AreEqual failed. Expected:<1>. Actual:<>
We failed on the assertion, just like we wanted.
That means we have a vaild red test! Let's make it green...
... Simplest possible.
Do the simplest thing.
Seriously, the simplest.
We only care about making this one test pass. Whatever we do that makes our tests green is valid.
We're going to return "1".
private string FizzBuzzIt(int v)
{
return "1";
}
Really.
It's the minimal we need to write to get the test to pass as per
- You are not allowed to write any more production code than is sufficient to pass the one failing unit test.
We've now written our first test!!!
Before we move on, we've had a red test, we made it green - Now to refactor!
We have an empty //Arrange
section in our test, let's refactor that.
[TestMethod]
public void ShouldReturnString1GivenInteger1()
{
//Arrange
//Act
string actual = FizzBuzzIt(1);
//Assert
Assert.AreEqual("1", actual);
}
I have an input value, and an expected value. Those are things we're 'arranging' for the test. Changing this up we'll get
[TestMethod]
public void ShouldReturnString1GivenInteger1()
{
//Arrange
int input = 1;
string expected = "1";
//Act
string actual = FizzBuzzIt(input);
//Assert
Assert.AreEqual(expected, actual);
}
Our current method looks like
private string FizzBuzzIt(int v)
{
return "1";
}
and ... well... v
isn't very informative. What can be a better name? ... mostly anything...
Doing this Kata we STILL have discussions about naming, it's not a simple thing. We lack a lot of context here which makes it more challenging to find a fitting name. I'm gonna go with toFizzBuzz
. it's a little ... informative...?
private string FizzBuzzIt(int toFizzBuzz)
{
return "1";
}
How useful is our method name? FizzBuzzIt
. It doesn't follow some of my other practices... but I'll leave it for now. I'm OK with this for our example.
Second Test
Clearly this implementation won't work for everything... and if we can think of an example it won't work for... That's a test we can write!
Like This:
[TestMethod]
public void ShouldReturnString2GivenInteger2()
{
//Arrange
int input = 2;
string expected = "2";
//Act
string actual = FizzBuzzIt(input);
//Assert
Assert.AreEqual(expected, actual);
}
This fails! YAY RED! OK, let's update our implementation... my way. As I learned from Woody Zuill.
When I'm doing TDD, ALL I care about is getting ONE red test to pass. In this situation we have only one red test, so that is my focus. As well as keeping everything else green.
My implementation to get the test to pass looks like this:
private string FizzBuzzIt(int toFizzBuzz)
{
if(toFizzBuzz == 2) return "2";
return "1";
}
This change is ONLY looking at the test. It's avoiding changing the rest of the implementation. If my efforts don't work, I can delete what I wrote and I'm back to a known state. If I instantly jumped to the 'obvious' solution return toFizzBuzz.ToString();
if I was wrong (in the non-trivial case), then I wouldn't have my original working code anymore. If I was deep into a rabbit hole... I might not be able to get back and would lose work when I did a git reset --hard
.
As I attribute to Kent Beck
All sins are permissible to get to green.
The requirement of that is the refactor phase.
Now, we can see a pattern, and know the 'better' solution, If an int comes in, make it the string. This is our triangulation. We've written tests for specific cases until we see the general form. Since we are green, we can refactor to that general case.
The way I like to do this is identical to how I make a test pass, add the general form so that if it's wrong, we can quickly jump back to the explict form.
private string FizzBuzzIt(int toFizzBuzz)
{
return toFizzBuzz.ToString();
if(toFizzBuzz == 2) return "2";
return "1";
}
Once we stay green, we can delete the two unreachable lines
private string FizzBuzzIt(int toFizzBuzz)
{
return toFizzBuzz.ToString();
}
This growth and contraction is a pattern we see in TDD. We create explicit cases which grow the method, and when we find the general case the method shrinks.
Final Example Test
If we're unable to think of another test to write that will fail - we should move on to additional requirements.
Let's go to the case of 3
which should return Fizz
.
[TestMethod]
public void ShouldReturnStringFizzGivenInteger3()
{
//Arrange
int input = 3;
string expected = "Fizz";
//Act
string actual = FizzBuzzIt(input);
//Assert
Assert.AreEqual(expected, actual);
}
I'm going to leave you with that. Follow the example of the test for 2=>"2"
. Then write the test for 6=>"Fizz"
.
Continue, see where you get. Please ping me on twitter or leave a comment here and I'll be happy to help you along.
Summary
This got to be MUCH longer than I expected, but I hope it was a helpful walk through.
The code up to that last test is available on my unit testing examples.
Have fun!