The recent discussion spawned by my former post has prompted me to write this entry about test types. There's not a single clear definition of what a unit test is, so sometimes people approach me with unit testing challenges that don't really relate to unit testing per se. The fact that you can use unit testing frameworks (such as Visual Studio 2005 Team System or NUnit) to drive not only unit tests, but also other types of tests, confounds the issue.

Although some people find such categorization semantic (or even pedantic), I distinguish between the following test types:

  • Unit tests subject a single unit to various stimuli in isolation from its volatile dependencies. In my terminology, when talking about .NET, a unit corresponds to an assembly, although other people view a unit as corresponding to a specific type. Viewing a unit as a single type is, in my opinion, too specific, as it effectively prohibits scenario-driven (or agile) development methods, since you will not be able to test the interaction between several types in the same assembly.
    Isolation from volatile dependencies is essential, because it enables you to develop your unit without having to rely on its dependencies.
  • Integration tests cover a wide variety of scenarios where you connect two or more units into an integrated subsystem. This covers the simple case where two units are used together in the same process; e.g. if you want to test the interaction between a UI controller unit and a domain logic unit.
    Integration tests can also span process or even machine boundaries, which is the case if you test a data access component against a relational database system, or if you test a web service by making requests against it using a proxy class.
    Sometimes I wish the terminology would allow us to distinguish between the in-proc and out-of-process scenarios, but they are all integration tests.
    Since integration tests involve the interaction of several moving parts, they are typically more difficult to set up to run as fully automated tests, although unit testing frameworks such as Visual Studio 2005 Team System are well suited also for this task.
  • System tests are by some also called acceptance tests. These test a system in its entirety, by using its intended interfaces - typically its user interface.
    Since the entire application, including user interfaces, is being tested, it is typically very difficult to run system tests in a fully automated fashion, because there is often much deployment and configuration to be done.

As you can see from the comments to my earlier post, people sometimes feel they have problems with unit tests, when in fact they have problems with integration tests. Integration tests are more difficult to configure for full automation, which is why I recommend that you try to test as much of your code as possible with unit tests.

For example, instead of testing a web service with an integration test where you make requests against the service using a proxy class, you should impement the service logic in a testable unit (i.e. a library), and then unit test that logic. Exposing the service as a web service will then be a simple case of implementing the Remote Façade design pattern, and you probably will not need to test the web service at all, because its implementation will be very simple.

That is not to say that I don't recommend integration or system tests - these can provide valuable insights into the interaction of multiple components that would be difficult to reproduce otherwise. Because integration and system tests are more difficult to automate than unit tests, you can get more value for your effort early in the development process with unit tests, but as you approach project completion, you need to begin testing the complex interactions.