Abstract
This article looks at unit testing patterns and describes the main patterns found in tested .NET code. It also describes the problems with each pattern. We will be using nUnit for our examples. For more information, see nUnit and Test Driven Development. Read the below nUnit tutorial to get more information on Nunit Unit testing and download Typemock Isolator for .NET unit testing.
Introduction
Programmers who have incorporated unit testing into their development process already know its advantages: cleaner code, courage to refactor, and higher speed. Writing tests is easy; it is writing good tests that makes automated unit testing a powerful tool. This article provides some basic testing patterns that help write good tests, although some cases may need special attention. But first, we must understand what a good test is. The following is a list of features of a good test:
- Isolation:
Tests should test only the code that we plan to test. We should not test underlying classes, which should be checked independently. Failure to test isolated code will lead to high coupling between tests, and changing a class may cause many unrelated tests to fail. The normal scope is a class, although sometimes it makes more sense to test a cluster of classes that are closely coupled (functionality wise). - Speed:
If running the tests takes too long, then developers are reluctant to run them. Therefore, we should keep our tests as fast as possible. - Self containment:
Tests that rely on external information are prone to fail and require configurations and setups. This leads to test discarding. To make sure that this doesn’t happen, all the information required for the tests should be in the tests. For example, instead of relying on an environment variable to be set, we must set it ourselves in the test. - Race safe:
Multiple developers should be able to execute tests at the same time. When a test depends on a shared state, it can lead to the wrong test results. For example, if there is a test that depends on and modifies a database and several developers are running it simultaneously, the data will be corrupted and the test will fail. - Independence:
Tests should not rely on other tests to be run before or after. They must be able to be run alone so that the developer can run a single test at a time. - Well documented:
Good documentation about what the test is doing will help understand the production code. Moreover, many developers don’t like to read the documentation and prefer to see an example of how to use the code. This is fine, and the tests can be a reference point. - Maintainable:
Tests should be seen as part of the software code even though it doesn’t make it to production. As such, it must be maintainable and refactored when needed.
Patterns, Patterns, Patterns
Throughout this article, we will use an example of an Authentication class. As it name suggests, this class is responsible for authentication in our system. Each pattern description is divided into three parts:
- When to use the patterns – what are the cases in which we will use the pattern
- How to use the pattern – what are the recommended steps of the pattern
- Example – example code in C#
We will now delve into the patterns.
Four-Stage Testing Pattern
When should it be used?
This is the normal testing scheme.
How should be it used?
- Setup prerequisite objects: Create the scenario of the test (this can be done in the test method or in the [SetUp] and[TestFixtureSetUp] methods).
- Call the method being tested.
- Evaluate the results.
- Tear down the objects.
Example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class Authentication { private string _key; public string Key { get {return _key;} set {_key = value;} } public string EncodePassword(string password) { // do the encoding return encoded_password; } } |
This is our test:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
[TestFixture] public class TestFixture1 { Authentication authenticator; String name; [SetUp] public void Init() { // set up our authenticator and key authenticator = new Authentication(); authenticator.Key = "TESTKEY"; name = "user"; } [TearDown] public void End() { // finish tests authenticator.Key = ""; } [Test] public void Encoding () { // continue specific test set up String expectedKey = "fwe94t-gft5"; // Call our method String recivedKey = authenticator.EncodePassword(name); // Validate that for "user" and "TESTKEY" key we should get our key Assert.AreEqual(expectedKey,recivedKey); } } |
Test-Exception Pattern
When should it be used?
When we expect our test to raise an exception.
How should it be used?
Use nUnit’s [ExpectedException] attribute.
Example
1 2 3 4 5 6 7 8 9 10 |
Our EncodePassword method throws an exception if the password is empty: public string EncodePassword(string password) { if (password==null || password.Length==0) { throw new ValidationException("Password is empty"); } // do the encoding return encoded_password; } |
The following code snippet shows an example of how to test exceptions:
1 2 3 4 5 6 |
[Test] [ExpectedException(ValidationException,"Password is empty")] public void Encoding () { authenticator.EncodePassword(null); } |
We expect our test to throw ValidationException with the “Password is empty” error message.
Inner-Class Test Pattern
When should it be used?
When a protected entity (field or method) needs to be accessed for the test. This can happen in two situations:
- When we need to test a protected method or access a protected field.
- When we need to override a public or protected method (can be done only if the method is virtual).
How should it be used?
Create a new class that extends our tested class.
To test a protected method, add a method in our extended class that calls the protected method (delegation).
To override a method, override this method in our extended class.
Example – Testing a protected method
The following code snippet shows an example of how to test the protected EncodePassword method.
1 2 3 4 5 6 7 8 |
class TestAuthentication : Authentication { public string CallEncodePassword(string password) { // We can call a protected method from here return EncodePassword(password); } } |
1 2 3 4 5 6 7 8 9 |
[Test] public void Encoding () { TestAuthentication authenticator = new TestAuthentication(); authenticator.Key = "TESTKEY"; String expectedKey = "fwe94t@#$5"; String name = "user"; // call the tested method by means of the Inner Class Assert.AreEqual(expectedKey,authenticator.CallEncodePassword(name)); } |
Example – Overriding a protected method
Let’s go back to our example. Suppose that Authentication has the following methods (the method DoSelect returns the number of lines that the one selected returned):
1 2 3 4 5 6 7 8 9 10 |
public bool IsAuthenticated(string name,string password) { // do something int Results = DoSelect(statement); return Results==1; } protected virtual int DoSelect(string statement) { // do something } |
We want to check that IsAuthenticated is behaving correctly but without setting up a database of passwords. We can override the DoSelect method and return our own values. We can then test if DoSelect works using the Inner Class Test Pattern for testing a protected method.
1 2 3 4 5 6 7 8 9 10 11 12 |
[Test] public void Authenticated () { TestAuthentication authenticator = new TestAuthentication(); // if 1 record found should be authenticated authenticator.returnAmount = 1; Assert.IsTrue(authenticator.IsAuthenticated("user","password")); // if no records found should not be authenticated authenticator.returnAmount = 0; Assert.IsFalse(authenticator.IsAuthenticated("user","password")); } |
Reflection Test Pattern
When should it be used?
When we need to test private methods.
In most cases, we don’t need to test private methods. It is enough to test the public ones, although there are some cases when this is needed. For example, if we have a perfect binary tree class with get and put methods, we can check that the tree works, but we still need to test that the tree is perfect. This can be done only if we test the private method that resorts the tree.
How should it be used?
Use reflection.
Example
1 2 3 4 5 6 7 8 9 10 11 |
[Test] public void Internals () { Type type = typeof (Authentication); Authentication authenticator = (Authentication)type.GetConstructor (System.Type.EmptyTypes).Invoke(null); bool field = (bool)type.GetField("privateField").GetValue(authenticator); Assert.IsTrue(field); bool result = (bool)type.GetMethod("PrivateMethod").Invoke(instance,null); Assert.IsTrue(result); } |
Mock Object Pattern
Mock objects are objects that replace the real objects and return hard-coded values. This helps test the class in isolation.
There is a difference between mocks and stubs. A stub is used to simulate an object. Stubs are typically used to stub out objects that are expensive to create or manipulate, whereas mocks are used to test interactions between classes. This is a major difference. There’s a good article by Martin Fowler about mocks and stubs:http://martinfowler.com/articles/mocksArentStubs.html.
Mocks are used to validate that the interactions between objects behave as expected.
Do not attempt to reproduce real behavior in a mock object; if they are becoming complex, revisit both the mock implementation and the code being tested to see if there is some intermediate concept to be factored out.
Dynamic mock frameworks are frameworks that build mock objects on the fly. There are quite a few mock object frameworks; the difference between them is in the way the developer writes the interactions with the object.
When should it be used?
Mock objects can be used in the following cases:
- The real object has a nondeterministic behavior (it produces unpredictable results, like a date or the current weather temperature)
- The real object is difficult to set up
- The behavior of the real object is hard to trigger (for example, a network error)
- The real object is slow
- The real object has (or is) a user interface
- The test needs to ask the real object how it was used (for example, a test may need to confirm that a callback function was actually called)
- The real object does not yet exist (a common problem when interfacing with other teams or new hardware systems)
How should it be used?
The three key steps to using mock objects for testing are:
- Use an interface to describe the object.
- Implement the interface for production code.
- Implement the interface in a mock object for testing.
Example – using the nUnit.mock framework
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
Suppose our code implements a callback each time authentication is called: IList subscriber; public void AddSubscriber(ISubscriber subscriber) this.subscriber.Add(subscriber); } public bool IsAuthenticated(string name,string password) { foreach (ISubscriber sub in subscriber) { sub.Recive(name); } // do something int Results = DoSelect(statment); return Results==1; } |
1 2 3 |
public interface ISubscriber{ void Receive(String message); } |
Here is a test:
1 | Create a mock for the ISubscriber interface. | |
2 | Get the actual object. | |
3 | Expect Receive() to be called once with the “user” string as a parameter. | |
4 | Verify that the mock was called as expected. |
There are mock frameworks that implement mock objects for common types (System.Data, for example).
Summary
In this article, we introduced the common unit test patterns. These patterns can be use alone or in combinations. For example, you want to mock a database connection but it is created in a protected virtual method. The test will use the Inner Class Pattern to return the mock database object with a mock object for the actual database.
There are still many situations in which these patterns are not sufficient and there is a need to change the code to make it testable. These include:
- Singleton classes
- Calls to static members
- Objects that do not have an interface (as required for mock-object pattern)
- Objects that are instantiated in the code being tested
- Objects that are not passes to the method (as required for mock-object pattern).
These will be covered in Part II – Type Mock Pattern.