The art of writing unit tests

With keeping up the quality of your code comes writing unit tests. In its origin, a unit test tests the behavior of a method of a certain class. People tent to fall into writing more integration of system tests, where they test several components at once. Unit tests then become very hard to maintain. What does a good unit test consist of and what do we test?

Properties of a good unit test

What are properties of a good unit test?

  • They should run fast
    If you need to wait very long tests are run, it’s likely that other people will skip running unit tests before committing code to the archive. Also when they run very slow, it can also be the case you have timing or other dependencies within your code (see bullet below).
  • They are repeatable
    Each time you run the unit test with the same conditions, you should get the same output. Since threads are very hard to configure in a way that the unit test can be repeated, threading is something you don’t want within your unit tests (see also next point).
  • Should not depend on external hardware or timing
    When timing needed, you should make a stub for your timing framework. In this case you can easily manipulate timing in your test. When you need to have disk access, also stub it. In this case you can fake a good write and a bad write to the disk and test if your code can handle these.
  • Should run independent from other tests
    If other tests needs to be run before the current test can be executed, your test isn’t designed properly.

Also beware of the fact that you only test the public properties. There should no need for testing private methods. If you need to, you should think of redesigning your class design. The data you put in your class, should result in some action to the out-side world. By verifying the output, you should be able to check if the private method did what it needed to do.

What to test

What do we need to test in order to verify the quality of our code?

  • Write basic test with an expected outcome
    The first tests you write should be tested with data you expect to be valid outcomes.
  • Boundaries
    Test the upper and lower boundaries of the values your method accepts.
  • Possible exceptions
    Try to get your code within an exception so you can also test if your code can handle these.
  • Null pointers as arguments
    How defensive is your code written? Can it handle null values properly?

Stubs and mocks

Making a class easy to be covered by a unit test, requires you to think of a good software design using the SOLID design principles. By making the class depend less on other classes and using interfaces if they have to, you can unit test a class easier. A mocking framework can be used to generate a stub or a mock of the interfaces. A mocking framework creates an instance of a class, defined by your interface, allowing you to use the concrete class within your unit test. An example of such a mocking framework is Rhino Mocks.

There are several ways to use the stubs within your unit tests and use real variants within your production code. Some examples:

  • Via the constructor
  • Using the factory pattern
  • Settings stubs via a method or property

Via the constructor

Dependency injection is used much for this, allowing you to provide the right instances to the constructor. In case you have multiple external classes communicating with your class you want to stub, the number of arguments you provide to your constructor can become very long.

Using the factory pattern

Within the factory pattern, set the stubbed variant via your factory. When create is called within the factory, the stubbed variant will be returned:

public class HardwareFactory
{

  private IDevice m_device;

  public IDevice Create()
  {
      IDevice device = null;
  
      if (null != m_device) { device = m_device; }
      else { m_device = new ConcreteDevice(); }

      return device;
  }

  public void SetDevice(IDevice device)
  {
    m_device = device;
  }
}

Settings stubs via a method or property

Looks similar to the factory, but then you combine the set-method within your class.

Code coverage

To check if indeed your tests covered all your code, you can use code coverage. A free solution for this is OpenCover. When there are lines that are not covered by your tests, you’re simply notified by the coverage report. In this way you can easily extend your tests so also the uncovered parts of code are now tested.

Posted in Uncategorized by Bruno at December 27th, 2017.

Leave a Reply