Unit Testing Fundamentals

I was surprised recently when I discovered some talented developers I know aren’t familiar with unit testing. They’re not aware of its value and don’t know the principles surrounding it.

I keep thinking, “How can this be?” I usually assume I’m the last to the party. But the thought occured to me, if these talented developers aren’t aware of why they should be unit testing and how to unit test, then they aren’t the only ones. There must be a lot of developers in the same boat.

What is a unit test?

A unit test is a method or function that has a purpose of testing a single thing in a single unit. A unit here is defined as a single method or function.

Why should we be unit testing?

A suite of unit tests ensures that your code works as expected and does not create regression issues, or new unforeseen issues. They increase development velocity by allowing developers to be fearless when it comes to refactoring existing code. It also allows you to catch bugs right in the active development phase when they are cheap. It is much more expensive to find your bugs whenn your code has been shipped to the QA team, and even worse when the code is shipped into production.

There are also ancillary benefits to unit testing. When you are writing code with unit testing in mind, the code is generally designed better. There are fewer moving parts and fewer side effects, because these things make it more difficult to write adequate tests. There’s also a smaller cyclomatic complexity (this refers to the number of branches in your code) because a higher cyclomatic complexity greatly increases the number of tests you need to write to have adequate code coverage.

I don’t think it’s easy to truly grasp unit testing before you’ve done it a few times — maybe even not until it’s saved your ass a few times or revealed some bugs that have gone unnoticed for ages. I certainly did not have an easy time grasping the concept and guidelines when I was first told about unit testing and its value. I’ll go over some guidelines for unit testing in a smart way, which should help you, but really creating some tests is the best way to learn what this is all about.

Guide Lines

Unit tests must be repeatable. If you cannot trust that a unit test will execute exactly the same way every time and get the same results every time, then you’re not ever going to be able to test the results of that test. This means staying away from the following in your tests: random number generation, race conditions, and the current date and time.

Unit tests are clean and readable. If a unit test is not maintainable and if you do not easily understand the purpose of a unit test and how it accomplishes this purpose, then you are not going to trust the test. If you cannot trust the test, then you won’t care about whether or not it passes or fails.

Unit tests execute quickly. If that unit test takes longer than 500ms to execute then you are never going to want to run it. Realistically, your unit tests are unlikely to come anywhere close to 500 ms in execution time, but I wanted to put a hard stop somewhere. If you do not want to run your test suite because the tests are slow, then you aren’t going to run it as often and you will notice bugs when they have a larger impact rather than when they are small and have just been written.

Unit tests cover a single unit (method or function). Anything larger than that is an integration test: these run longer and require significantly more setup and teardown.

Unit tests should be able to be run in any order and should not contain dependencies. Any dependencies needed in the code should be mocked out with a mocking framework to ensure that you are only testing the logic in a single unit and are not inadvertantly creating an integration test.

How to Write a Unit Test

Unit tests are typically composed of three pieces: Arrange, Act, and Assert. Arrange is the setup and configuration piece. Anything you need to do to prepare for the action you are testing — parameter object creation, etc. Act is where you are actually calling the unit being tested. Assert is where you test the result of the action.

When writing a unit test, it is highly recommended that you have no more than one assert statement per unit test. A lot of unit test frameworks will halt as soon as a single assert has failed. If you have multiple asserts in a test, this can prevent you from getting complete information on your failure. What if you fix the failure, only to find that the second assert then fails? When unit tests are more specific, they are less flexible and need to be updated more often which can be a drain on your development resources.

Tests should not contain new logic. Since all logic should be covered by unit test, you are increasing the likelihood of a bug existing in the test itself. You may have heard the phrase, “Who watches the watchment?” Well, who tests the tests?”

Magic values should not exist solely in method scope. It makes it more difficult to check the value as a result of the test. This means that if the value needs to be updated in the future, you’d also need to update the test. This shouldn’t be the case. You should try to have your unit tests focus on the program logic instead of on specific business values driving that logic.

DRY with Unit Testing

What if you wanted to unit test that the dependency resolver was able to resolve dependencies and create objects? This would mean that you would need to load a unity container. You would also want it to register the needed types with that unity container. Keep in mind that we are using the UnityConfig as provided by the solution.

[TestClass]
public class UnityTest
{
    public UnityTest()
    { }

    [TestMethod]
    public void OcrControllerCreationTest()
    {
        IUnityContainer unity = new UnityContainer();
        OcrReader.App_Start.UnityConfig.RegisterTypes(_unity);
        OcrController ocrController = unity.Resolve<OcrController>();
        Assert.IsNotNull(ocrController);
    }
}

You can see that the unit test meets all of the criteria. It is: short, fast, starts with setup, executes the code, and validates the result. What happens when we want to test a different controller?

[TestClass]
public class UnityTest
{
    public UnityTest()
    { }

    [TestMethod]
    public void OcrControllerCreationTest()
    {
        IUnityContainer unity = new UnityContainer();
        OcrReader.App_Start.UnityConfig.RegisterTypes(_unity);
        OcrController ocrController = unity.Resolve<OcrController>();
        Assert.IsNotNull(ocrController);
    }

    [TestMethod]
    public void OcrApiControllerCreationTest()
    {
        IUnityContainer unity = new UnityContainer();
        OcrReader.App_Start.UnityConfig.RegisterTypes(_unity);
        OcrApiController ocrApiController = unity.Resolve<OcrApiController>();
        Assert.IsNotNull(ocrApiController);
    }
}

Both unit tests meet all of the criteria of unit tests, but both of them have two lines that are identical. We don’t want to factor out anything critical from a unit test, it should always remain readable without needing to just between a lot of methods, but this is just setup. And if we were to add another controller, we’d be repeating those lines again. How do we take care of this to avoid repeating shared setup? We can add test setup and tear down methods.

[TestClass]
public class UnityTest
{
    private IUnityContainer _unity;

    public UnityTest()
    { }

    [TestInitialize]
    public void Init()
    {
        _unity = new UnityContainer();
        OcrReader.App_Start.UnityConfig.RegisterTypes(_unity);
    }

    [TestCleanup]
    public void Cleanup()
    {
        _unity = null;
    }

    [TestMethod]
    public void OcrControllerCreationTest()
    {
        OcrController ocrController = _unity.Resolve<OcrController>();
        Assert.IsNotNull(ocrController);
    }

    [TestMethod]
    public void OcrApiControllerCreationTest()
    {
        OcrApiController ocrApiController = _unity.Resolve<OcrApiController>();
        Assert.IsNotNull(ocrApiController);
    }
}

Unit tests are now even smaller and still just as readable. Every test will have the unity container instantiated and set up before it runs, and emptied again when it has finished running. You can also do this on a Class scope instead of a Test scope, but I prefer to have as tight a scope as possible.

What is mocking?

There are different libraries that you can make use of to mock out interfaces when performing a unit test, this allows you to break dependencies that exist in production. Two popular ones for .NET are Moq and NSubstitute, both available as NuGet packages. I prefer since there are times when I’ve had trouble with Moq’s syntax in odd situations, but have not had the same issues with NSubstitute.

Here’s a unit test that makes use of NSubstitute:

private ILog _log;
private Logger _logger;

[TestInitialize]
public void Initialize()
{
    _log = Substitute.For<ILog>();
    _log.IsInfoEnabled.Returns(true);
    _logger = new Logger(_log);
}

[TestMethod]
public void Info_NoSupportCode()
{
    _logger.RequestID = REQUEST_ID;
    _logger.Info(TEST_MESSAGE);
    _log.Received(1).InfoFormat(BASIC_TEMPLATE, SUPPORT_CODE_FORMAT, TEST_MESSAGE);
}

_log is an NSubstitude substitute. It’s an instance of ILog and all that we do with it in the setup is make sure that if IsInfoEnabled is checked, it always returns true. _logger.Info calls IsInfoEnabled, so if it’s not set, this wouldn’t work. We’re also checking that InfoFormat was called on the ILog substitute. Now, that method wasn’t hooked up to actually do anything, but NSubstitute will count how many calls it receives and which parameters it receives it with.

This is good because it allows us to test side effects. It’s a requirement that _logger.Info call _log.InfoFormat. It’s not an output so we can’t test the output. If a side effect needs to occur in a method, you can (and should) use NSubstitute to ensure that it is happening. You can also ensure that a call was not received, which is also needed for testing branching code.

How many tests should you write?

My rule of thumb is that any method that takes in an int should have at least 7 tests.
1] Definitely a pass.
2] On the inside of the bottom edge.
3] On the outside of the bottom edge.
4] On the inside of the top edge.
5] On the outside of the top edge.
6] Int32.MinValue
7] Int32.MaxValue

Generally speaking, you need unit tests to cover every possible branch of your method. You also need tests to cover your edge cases and failure cases.

Remember, this isn’t just to ensure that you’ve done it correctly — although my unit tests have caught plenty of my own problems before I’ve pushed them to others — your unit tests are there so that when other people make changes to your code in the future, they don’t break the intent of the code and accidentally cause regression issues. Unit tests allow us to document the intent of a method in a way that is more likely to be kept up to date than comments.

If a new bug is discovered, write a new unit test to cover it. This should help ensure that we do not keep running into it with future refactors.

Summary

Unit tests are small tests that cover a single unit of code. Your unit test suite should cover all of your program logic but contain no logic of its own. Unit tests can be invaluable in early detection of bugs and ensuring that the code written meets the actual intent of the code. They have the added benefits of encouraging good code design and making you consider all the ways in which your code might fail. More than that, they help ensure that when other developers refactor your code in the future they do not break the intent of the code.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s