Beginner's Guide to Unit Testing

Published Oct 26, 2015Last updated Feb 15, 2017
Beginner's Guide to Unit Testing

Unit Testing Fundamentals

Unit testing is a critical skill for modern software developers. It's now considered a standard interview topic. This post will cover what you need to know (and should practice!) to gain unit testing confidence

What is Unit Testing

Unit testing is the code you write to test your code. It's used to ensure the quality of your working product. Imagine being a chef who never tastes your own food. Seems crazy, right? Unit testing is what you do as a developer to ensure your code is working as desired.

Your tests are also used by other developers to learn how to use your code. They can read and run your tests to learn how it works. A full suite of unit tests is often more useful than documentation or large amounts of comments.

Finally, your tests ensure that changes don't unintentionally break existing code through regression testing. A regression defect is a bug that breaks existing working code, and your unit tests are run to check for regression defects.

AAA: Arrange, Act, Assert

Unit testing follows a pretty common strategy - AAA, or Arrange, Act, Assert. First, arrange all preconditions for your test to run. Next, Act or execute your code. Lastly, assert the correct things happened.

Examples in C

Let's consider this basic class which will take an array of integers and return its sum:

public class SimpleMath
{
   public int Add(List<int> integers)
   {
      int sum = 0;
      integers.ForEach(x => sum += x);
      return sum;
   } 
}

There are a lot of fun discussions to be had around this simple class.

  • What if the integer list is null?
  • What if it's an empty array?
  • What if the summed value is greater than Int.MaxValue?

Your First Test

I always do a constructor test first. This is a simple test to show how the class is constructed.

[TestMethod]
public void ConstructorTest()
{
   // Arrange

   // Act
   SimpleMath math = new SimpleMath();

   // Assert
   Assert.IsNotNull(math);
}

Notice there's no arrange section. This is because it's a parameterless constructor. If the constructor required parameters, this is where I'd build the prerequisites to create the class. I simply create an instance of the class and assert it was created (not null).

Creating an instance of your class with external dependencies (database, web service, etc) is beyond the scope of this article, so I will cover mocking external dependencies later.

Null Parameter Test

What happens if the integer list is null? Way too much code is spent on null-checking values. A lot of legacy code will null check and suppress or apply a default value, but the real issue is the value not being set (or set correctly) initially. Let's put that discussion aside for the moment. Can our class handle a null list?

[TestMethod]
public void EmptyListTest()
{
  SimpleMath math = new SimpleMath();
  List<int> values = null;
  var result = math.Add(values);
  Assert.AreEqual(0,result);
}

What behavior do we want for a null list? This is not an easy decision. A null list could be considered a previous error where a list, even if empty, wasn't created correctly. A null list could also be considered the same as an empty list. This test expects the list to return 0, meaning a null list is treated the same as an empty list.

However, the test fails:

EmptyListTest threw exception: 

System.NullReferenceException: Object reference not set to an instance of an object.

We need to change our method to check for a null list and return 0.

public int Add(List<int> integers)
{
  if (integers == null) return 0;
  int sum = 0;
  integers.ForEach(x => sum += x);
  return sum;
}

Personally, I cringe when I see this. It makes an assumption that the caller intended to send in a list and expect a 0 back.

I prefer to throw an exception forcing any caller to call the method correctly:

public int Add(List<int> integers)
{
  if (integers == null)
    throw new ArgumentNullException("integers cannot be null");
  int sum = 0;
  integers.ForEach(x => sum += x);
  return sum;
}

This changes the unit test to expect an exception

[TestMethod, ExpectedException(typeof(ArgumentNullException))]
public void EmptyListTest()
{
  SimpleMath math = new SimpleMath();
  List<int> values = null;
  var result = math.Add(values);
}

Handing an Out of Bounds Test Case

What if your class has a bounds issue? A list could contain very large numbers which, when summed, will be larger than the integer return value. This is the failing unit test:

[TestMethod]
public void OutofBoundsTest()
{
   // Arrange
   int lessThanMax = Int32.MaxValue - 5;
   List<int> values = new List<int>() {lessThanMax, 3, 3};

   // Act
   SimpleMath math = new SimpleMath();
   int sum = math.Add(values);

   // Assert
   Assert.IsTrue(sum > Int32.MaxValue);
}

If you inspect the return sum, you'll find it's a large negative number, (-2147483648 in my case). How would you handle this case? It's an interesting problem and happens more often than you would think in real world programming. I would love to hear back from you on your solutions to this issue. Think about it and send me your solutions.

Also, let me know if you found this useful. You can find out more about me here on codementor.io or at my personal site wbsimms.com

Discover and read more posts from Barrett Simms
get started
Enjoy this post?

Leave a like and comment for Barrett

2
Be the first to share your opinion

Get curated posts in your inbox

Read more posts to become a better developer