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);
}
Handling 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