When Things Go Wrong – Unit Tests

Updated: Jul 5, 2020

In a previous post, you have learned how to debug your code when something unexpected happens. You also learned to use the TRY…CATCH clauses to handle exceptions that you are expecting. Unit tests provide you with a mechanism to ensure your application functions the way you expect.

Writing unit tests immediately after writing a segment of code will ensure that all of your code works as planned and is protected from future updates to your application.


Unit Test Explanation

What is a unit test?

A unit test is a function in code that will isolate a specific “unit” in your code and run test scenarios against it ensuring that the code functions as designed. Unit tests are focused on single units. They do not test how the different units work together. That is a different style of testing that usually uses a different framework. This post will focus on unit tests since they are the best way to introduce new developers into writing tests for their software.


What is a unit?

When dealing with unit testing, a “unit” is the segment of code in your extensive application. When working with C#, methods are generally considered the units for testing. To ensure they can be tested, the methods in your application should be written to do a specific task that can be tested as a single unit.


Why do unit testing

Writing unit tests to check the functionality of your code while you write it is the best way to ensure your application will work the way you intended. Well written tests will give you confidence and positivity that your application does what it should. Also, having unit tests will allow you to find unexpected issues with code earlier in the process.


A side effect of writing unit tests for all your methods is that it will force you to write smaller methods with reduced complexity, improving the readability and reuse of your code.

In my opinion, the most crucial reason to have unit tests is that it protects your code from the next person that updates the application. After they make their changes, they can run your tests. If any of your tests fail, they know they broke something, and they need to fix it.


What to test for

You must understand that unit testing is not about finding bugs in the code. Unit tests only work with a single unit. Application bugs usually occur when methods are working with each other; that is not the scope of a unit test. The only bugs unit tests will find occur when someone refactors the application after you have created the tests and written the code to make them pass.


When writing unit tests for your method, there are several scenarios for which you should test.


Happy Path

The first thing you should always test for is the scenario where valid data is being passed into the method, and no exceptions or errors are projected. This is often referred to as the “default scenario” or “happy path.” These tests simulate the expected behavior of the application user. Some developers only write tests for the happy path, but this could lead to upset clients and software downtime.


Unhappy/Sad/Bad/Exception Path

There is no agreed-upon term for the opposite of the “happy” path. You will hear some call it the “sad” or “bad” path. Others will call it the “exception” path since most of the test cases are ensuring that an exception is being thrown. These tests will be harder to write because you won’t always know what could cause your method to throw an exception. This is why it is a good idea to have test strategy sessions before writing your unit tests. In these sessions, you will discuss scenarios, workflow, and personas. Conducting a property test strategy is not in scope for this post. The takeaway here is that a good developer does not only test for the happy path.


Adding a Test Project

To write unit tests for your project, you will need to add a class library to your solution. The name of the class library doesn’t matter. However, you should make it clear that the purpose of the library is to hold unit tests for the solution.


My general practice is to house all the tests for one project inside a class library dedicated to that project. I then give the class library the same name as the project with a suffix of “.Tests”. I also name the classes in a way that I can easily see what tests that class will contain.

After creating the project that will contain the unit tests, you will have to make sure the class library can access the methods within the main project. If you are unsure how to add a project reference, read our post on Class Libraries.

Testing Frameworks

Once you have your test project created and referencing the main project, it’s time to determine what testing framework you are going to use. A testing framework provides you with all the classes and methods that you will need to create and execute your tests.


The three most common testing frameworks for Visual Studio are xUnit, NUnit, and MSTest MSTest is already part of Visual Studio, so no additional libraries are needed. However, some developers feel there are issues with its performance. NUnit and xUnit.NET (common name xUnit) are independent libraries that need to be added to your project.


My familiarity is with NUnit, so that this post will use that framework. However, the concepts of unit testing are universal and can be implemented regardless of the framework you choose.


NuGet Packages

The easiest way to get the libraries for NUnit into your project is to install the NuGet packages for them. A NuGet package is a bundle of related DLLs compiled together in a “package” that can be installed into your project.


To create and execute unit tests with NUnit, you will need to install the following packages into your testing project.

  • NUnit – this allows you to create the tests

  • NUnit3TestAdapter – this allows you to execute the tests

Writing a Unit Test

Before getting into the technical steps required for writing a unit test, we need to talk about how to structure your tests. A prevalent pattern for structuring a test is the Arrange-Act-Assert (AAA) pattern. This pattern provides a simple three-step process to author your unit test. This pattern is used throughout the industry and recommends the division of the test method into three sections. Each section is responsible for the action for which they are named.


Arrange

The Arrange section of your test method will contain only the code that is needed to set up the test. This section is where you initialize objects as well as provide values to variables the test will utilize. Any prerequisites your test needs to execute is handled in this section.

Act

The Act section is where you execute the code you are testing. You use the objects and variables that were set up in the Arrange section and call the method you want to test passing whatever parameters are needed by that method.


Assert

In the Assert section is where you will ensure the expectations of the test are met. It is here where you verify that the result of the method call matches your expected result


Example Test Method:

For our example, we are going to use a small application that has the hero drink potions that affect their stats.

 public static void ConsumeRedPotion(Dictionary<string, int> heroStats)
 {
 	Random random = new Random();
 	int strength = random.Next(1, 5);
 	heroStats["Strength"] += strength;
 
 	int intelligence = random.Next(1, 5);
 	heroStats["Intelligence"] -= intelligence;
 
 	int dexterity = random.Next(1, 5);
 	heroStats["Dexterity"] -= dexterity;
 }

The ConsumeRedPotion method is written to increase the strength by a random number and reduce the intelligence and dexterity. To properly test the happy path for this method, we will write three unit tests one test to check that each stat is being updated.


Unit Test Naming

The traditional method naming conventions don’t apply when you name your test method. Instead of short and concise, test method names should consist of three main pieces of information.

  1. The method name – what process are we testing

  2. The scenario - what is the situation being tested, what is the state of the application

  3. The expected behavior – if everything is coded correctly, what will be the result of the test

Following the best practices for naming our method, we will create our first test method.

 public class TestsForProgramClass
 {
 	public void ConsumeRedPotion_WhenCalled_IncreasesValueOfStrengthKey()
 	{
 
 	} 
 }

Before Visual Studio recognizes this as a test method, we need to add some attributes to our class and method. Attributes provide a way to add metadata or information to your codebase. They are generally enclosed within square brackets []. Before you can use any of the attributes of the testing framework, you need to make sure Visual Studio knows where to find the meaning of the attributes.


You have already added a reference to the framework by installing the NuGet package. You also need to tell Visual Studio that this class is “using” the code it provides.

using NUnit.Framework;

TestFixture

The first attribute you need is used at the class level. TestFixture will tell Visual Studio that the class contains test methods that need to be run by the testing framework.


Test

Now that Visual Studio knows this class is a TextFixture, it will be looking for methods. Adding the TestMethod attribute to your method will mark it as a test to be run.

 [TestFixture]
 public class TestsForProgramClass
 {
[Test]
 public void                      ConsumeRedPotion_WhenCalled_IncreasesValueOfStrengthKey()
 {
 
 }
 
 }	

Now that we have the proper attributes in our class, it’s time to write the test.

 [Test]
 public void ConsumeRedPotion_WhenCalled_IncreasesValueOfStrengthKey()
 {
 // ARRANGE
 // The method needs a dictionary with a string key and an int value
 Dictionary<string, int> testCollection = 
                 new Dictionary<string, int>();
                 
 // There must be the following keys in the collection: 
 // Strength, Intelligence, Dexterity
 testCollection.Add("Strength", 0);
 testCollection.Add("Intelligence", 0);
 testCollection.Add("Dexterity", 0);
 
 // ACT
 Program.ConsumeRedPotion(testCollection);
 
 // ASSERT
 // If the method ran successfully the value will be greater than zero
 Assert.IsTrue(testCollection["Strength"] > 0);
 }

In this example, you see the three segments of the suggested test structure.


In the ARRANGE section, I create the sample data that is needed for the test to execute. Under ACT, I call the method to be tested. In the ASSERT section, I make sure the value in the dictionary has increased. If the number increases, the test will pass; if not, it will fail.

The test method was one example of a test that can be written for the happy path. However, as stated earlier, a good developer will test the unhappy path as well.


Exception Path

Our happy path test has shown us that the method is expecting a key with the name of “Strength” to work correctly. What do we expect the method to do if that key is missing? Since there is no TRY…CATCH inside the ConsumeRedPotion method. The expected behavior is that an exception will be thrown if the key is missing. To be more specific, the “KeyNotFoundException” should be thrown. Let’s write a test method to make sure this exception occurs.

 [Test]
 public void ConsumeRedPotion_WhenMissingStrength_ThrowsKeyNotFoundException()
 {
 // ARRANGE
 // The method needs a dictionary with a string key and an int value
 Dictionary<string, int> testCollection = 
                 new Dictionary<string, int>();
 // Only add the Intelligence, Dexterity keys
 testCollection.Add("Intelligence", 0);
 testCollection.Add("Dexterity", 0);
 
 // ACT/ASSERT
 // A KeyNotFoundException should be thrown
 Assert.Throws<KeyNotFoundException>
 (
 () => Program.ConsumeRedPotion(testCollection)
 );
 }

This test method will pass if the KeyNotFoundException is thrown when the test is run. Notice that the ACT and ASSERT sections are combined. This practice is an acceptable design when the ASSERT needs to call the method that is being tested.

Test Cases

In addition to the TestFixture and Test attributes, NUnit has a third attribute that is very helpful when writing test methods. The TestCase attribute allows you to run the same test method multiple times with different data. You accomplish this by adding parameters to your test method and providing different arguments withing TestCase attributes.


To show you how test cases work, I’m going to make a small change to the ConsumeRedPotion method.

 public static void ConsumeRedPotion(Dictionary<string, int> heroStats)
 {
 Random random = new Random();
 int strength = 5;
 heroStats["Strength"] += strength;
 
 int intelligence = random.Next(1, 5);
 heroStats["Intelligence"] -= intelligence;
 
 int dexterity = random.Next(1, 5);
 heroStats["Dexterity"] -= dexterity;
 }

I have removed the randomness of the “strength” variable and set it to a standard value of 5. The test I wrote earlier will pass because strength is still increasing. Now I want to write a test to ensure the new value of the “Strength” key increases by five every time the method executes. I can do this with the TestCase attribute.

 [TestCase(5,10)]
 [TestCase(37, 42)]
 [TestCase(561, 566)]
 public void ConsumeRedPotion_WhenCalled_IncreaseStrengthKeyByFive
(int value, int result)
 {
 // ARRANGE
 // The method needs a dictionary with a string key and an int value
 Dictionary<string, int> testCollection = 
         new Dictionary<string, int>();
 // There must be the following keys in the collection: 
 // Strength, Intelligence, Dexterity
 testCollection.Add("Strength", value);
 testCollection.Add("Intelligence", 0);
 testCollection.Add("Dexterity", 0);
 
 // ACT
 Program.ConsumeRedPotion(testCollection);
 
 // ASSERT
 // If the method ran successfully the value will match the result
 Assert.AreEqual(testCollection["Strength"],result);
 }

This test method will run three times, checking the assertion each time.


Now that we have learned about test cases, let’s refactor our exception test method to run three times each time with a different missing key.

 [TestCase("Strength","Intelligence")]
 [TestCase("Strength", "Dexterity")]
 [TestCase("Dexterity", "Intelligence")]
 public void ConsumeRedPotion_WhenMissingStrength_ThrowsKeyNotFoundException
 (string keyOne, string keyTwo)
 {
 // ARRANGE
 // The method needs a dictionary with a string key and an int value
 Dictionary<string, int> testCollection = 
                 new Dictionary<string, int>();
 // Only add the Intelligence, Dexterity keys
 testCollection.Add(keyOne, 0);
 testCollection.Add(keyTwo, 0);
 
 // ACT/ASSERT
 // A KeyNotFoundException should be thrown
 Assert.Throws<KeyNotFoundException>
 (
 () => Program.ConsumeRedPotion(testCollection)
 );
 }

Running Unit tests

Once you have written your test methods, you can execute the tests without running the application by utilizing the Test Explorer. You can open the Test Explorer by clicking Test -> Windows -> Test Explorer. In the Test Explorer, you can run all your test or select certain ones to run. Tests that pass will have a green check mark, ones that fall will have a red X.

When you first write your unit tests, everyone one of them should pass. If any of your tests fail, you either made an error in writing the test, or you don’t fully understand what the method you are testing is doing. Double-check everything and make the necessary changes to ensure your tests pass.


67 views0 comments

Recent Posts

See All