by Zoran Horvat
Most of the objects that we use are stateful. This means that object contains some state and typically exposes some operations. Object-oriented design teaches us how to encapsulate data and let clients in only through public methods of an object.
This whole process is based on an idea that object is responsible for data it contains. It exposes operations so that clients can interact with its state, but direct access to the state is kept for that object alone.
And with that responsibility comes another one: Object must ensure that its internal state is consistent and valid. It must protect itself from receiving invalid or incomplete data when that can harm consistency of its internal state.
In this article I will demonstrate an effective way to protect the object from being initialized in an invalid state. Next I will demonstrate how to easily assemble unit tests that prove that validation logic executed during object initialization works correctly.
One problem which frequently occurs in practice is to have a class like this:
public class Frame
{
private int length;
private int width;
public int Length
{
get
{
return this.length;
}
set
{
if (value <= 0)
throw new System.ArgumentException();
this.length = value;
}
}
public int Width
{
get
{
return this.width;
}
set
{
if (value <= 0)
throw new System.ArgumentException();
this.width = value;
}
}
}
This class represents some kind of a two-dimensional frame, defined by integer dimensions which must be strictly positive. Validation is added to property setters and it is executed when object is initialized:
Frame frame = new Frame();
frame.Length = 3;
frame.Width = 4;
But this design suffers from several problems – one of them is that object exposes its internal state instead of providing operations on that state. But there is also an issue with the first line of code, in which new instance of the Frame class is created. This instance initially has dimensions set to zero – this is done by the implicit default constructor. In other words, initial state of the object is invalid.
Problem with this solution is that programmer must not forget to assign values to the properties after initializing the object or otherwise frame object will be left with invalid dimensions.
An easy way to solve the problem is to introduce a meaningful constructor. Suppose that this class also needs to support drawing, which is accomplished through an object implementing some interface IDrawingContext:
public interface IDrawingContext
{
void DrawRectangle(int x, int y, int length, int width);
}
Dimensions of the frame and the drawing context need to be passed through the constructor:
public class Frame
{
private int length;
private int width;
private IDrawingContext context;
public Frame(int length, int width, IDrawingContext context)
{
if (length <= 0 || width <= 0)
throw new ArgumentException();
if (context == null)
throw new ArgumentNullException("context");
this.length = length;
this.width = width;
this.context = context;
}
public void Draw()
{
this.context.DrawRectangle(0, 0, this.length, this.width);
}
}
In this implementation Frame class does not expose its internal state. Instead, it exposes an operation which draw the frame using specified drawing context. This is much better design because clients can rely on an instance of the Frame class when they need to have the frame drawn. Clients never really care about internal state of the Frame object as long as Frame itself cares about it.
Suppose that we want to cover the Frame class with tests. First step is to separate its functionalities. One functionality is to initialize the object. Another functionality is to draw the frame using the supplied drawing context.
I will test these functionalities in the opposite direction, by first testing the Draw function.
Basic requirement for the Draw function is that it collaborates correctly with the drawing context. This means that the drawing context’s DrawRectangle method is invoked and that correct size and position of the rectangle are passed to it. Here is the test method which encapsulates this piece of logic:
[TestMethod]
public void Draw_InvokesDrawOnDrawingContextWithCorrectArguments()
{
Mock<IDrawingContext> mockedContext = new Mock<IDrawingContext>();
mockedContext.Setup(c => c.DrawRectangle(0, 0, 3, 4)).Verifiable();
IDrawingContext context = mockedContext.Object;
Frame frame = new Frame(3, 4, context);
frame.Draw();
mockedContext.Verify(c => c.DrawRectangle(0, 0, 3, 4));
}
This method relies on the MSTest testing framework which can be recognized by the TestMethod attribute. Using any other testing framework will be more or less the same in this simple case – e.g. to use nUnit just replace the TestMethod attribute with nUnit’s Test attribute and it will work fine.
In this test I am also using the Moq mocking framework. The first three lines of the test code are setting up the expected behavior in terms that drawing context instance expects its Draw method to be invoked with specific coordinates and dimensions of the frame. Next step in assembling the object under test is to instantiate the Frame class. Frame object has length equal to 3, width equal to 4 and mocked drawing context as its arguments.
The following step is to actually invoke the frame’s Draw method and that is supposed to trigger the expected call to the drawing context. The last line of the test method asserts the expectation. Should the Frame fail to pass the call to drawing context’s DrawRectangle method, or should it fail to pass the arguments correctly, the test would fail.
The whole point about this test method is that it tests behavior of the Frame. We don’t care whether constructor has stored rectangle’s dimensions properly in its internal state or not. Maybe the Frame has delegated that to some other class, or to the database. In that case Frame would have to load its dimensions back when its Draw method is called. That doesn’t matter at all to this test, because it only asserts that, with assumption that drawing context class is implemented properly, the rectangular frame is going to be drawn where it should be. That is the proper behavior test, as opposed to state test which would assert that internal state of the Frame contains correct dimensions.
I am normally reluctant to implementing state tests because they often miss the point. State is encapsulated in the Frame object. Forcing the Frame object to expose its internal state just to allow the test to see it is wrong because it commits the Frame to specific kind of internal state. What I am more eager to test is whether Frame object with specific dimensions interacts with its collaborators correctly. That leaves the Frame class all the freedom it needs to implement its expected behavior right.
And now we come to the central point of this article: Testing the constructor. By this point we have found that Frame class collaborates with drawing context correctly, given the fact that Frame object itself is well built. But this relies solely on the constructor which has a responsibility to ensure that state it receives is validated. Its other responsibility is to remember that state, but as I already pointed out that is the subject of behavior tests. Any failure of the constructor to record its arguments will become visible through incorrect behavior.
The bottom line is that constructor tests should only cover the validation logic. In case of the Frame class, we recognize these cases:
This makes total of seven test cases. In traditional manner, I would now start typing seven distinct test methods. Those methods that expect an exception back would declare that expectation via a test method attribute. Those that don’t expect exceptions would skip the attribute. For example:
[TestMethod]
[ExpectedException(typeof(System.ArgumentNullException))]
public void Constructor_ReceivesNullIDrawingContext_Throws()
{
new Frame(3, 4, (IDrawingContext)null);
}
[TestMethod]
public void Constructor_ReceivesLength1AndWidth1_Passes()
{
IDrawingContext context = new Mock<IDrawingContext>().Object;
new Frame(1, 1, context);
}
These test methods look a little odd. They don’t assert anything. That is very common picture when testing constructors, because we are only interested to know whether exceptions are thrown when they need be thrown and whether no exception occurs when data are valid. The second test method, for example, passes if nothing happens when constructor is invoked.
Anyway, I am not going to proceed with writing these tests right now. Instead, I plan to take a different route.
Testing the constructor by enumerating all test cases in separate test methods has a couple of, so to say, human problems. First of all, they require quite a lot of typing to construct the methods and embellish them with proper attributes. Second, covering a constructor with a handful of test methods means that test cases for one constructor will span more than one screen of source code in height. These two issues combined lead to frustration (due to excessive typing) and faults (due to forgetting one or two legitimate test cases).
Frustration becomes even more observable as the project progresses and number of constructors that need to be covered with tests starts to pile up. If you know that even a small project has dozens of classes, while normal-size industry projects contain hundreds of classes, testing constructors becomes one boring routine. And with routine in coding comes the routine of making mistakes.
Instead of having each of the test cases wrapped in a separate test method, which wastes screen and spreads actual testing logic over many lines of code, I wanted to have something that brings test cases together. Something similar to the bulleted list above where I have just listed the cases without talking too much about them. If I could list all seven test cases in successive seven lines of code, then it would be quite easy to say whether all the cases have been covered, or some of them are missing.
To cut the long story short, here is what I want to have:
[TestMethod]
public void Constructor_FullTest()
{
// ...
}
I want all the tests for the constructor to be concentrated in only one test method. I don't want to have to type seven test methods.
On a related note, some unit testing purists would object to this, with some ground to it. The whole point of unit tests is to test a single requirement for a single method. If you try to put two assertions in one test method, you have a problem when test fails. If first assertion failed, then the second one is not even tried. Hence, you don't have the complete picture of what failed. You fix the problem, just to find that now the second assertion fails. That is why it is a good practice to have only one assertion in one test method.
But, I often put several tests in one test method, just to compact the testing code and make it more readable. It doesn't say that I'm putting more than one assertion into one test method. No; instead, I internally perform multiple tests and then build a list of error reports before letting the whole test method to fail. Because of that, I believe that concentrating testing code in one test method is a good practice, provided that test cases covered by that method are closely related. I will demonstrate what it means on example of testing the Frame class constructor.
I want this constructor test to be as compact as possible, but still to contain complete and precise information about cases in which constructor should fail or succeed. While doing that, I want to you to imagine me as being lazy and spoiled. In terms of writing code, I find those two characteristics helpful more often than not. I want my code to be compact - that is the lazy part - and I want all the information readily available - that is the spoiled part.
One of the most promising approaches to writing compact code is coding declaratively. This means that I would only need to list the constructor use cases and something buried deep inside some library code will do exactly what I wanted.
You will know what I mean as soon as you see the piece of code listed below. Note that it is based on types and methods that still do not exist. First I want to outline the solution, and then I will implement the library classes which move that solution to life.[TestMethod]
public void Constructor_FullTest()
{
IDrawingContext context = new Mock<IDrawingContext>().Object;
ConstructorTests<Frame>
.For(typeof(int), typeof(int), typeof(IDrawingContext))
.Fail(new object[] { -3, 5, context }, typeof(ArgumentException), "Negative length")
.Fail(new object[] { 0, 5, context }, typeof(ArgumentException), "Zero length")
.Fail(new object[] { 5, -3, context }, typeof(ArgumentException), "Negative width")
.Fail(new object[] { 5, 0, context }, typeof(ArgumentException), "Zero width")
.Fail(new object[] { 5, 5, null }, typeof(ArgumentNullException), "Null drawing context")
.Succeed(new object[] { 1, 1, context }, "Small positive length and width")
.Succeed(new object[] { 3, 4, context }, "Larger positive length and width")
.Assert();
}
Even without explanations I believe that intention of this test method is clear.
First I am declaring that I want to test constructor of the Frame class by using the still nonexistent static class ConstructorTests.
Then I am isolating the constructor which receives two integer numbers followed by the drawing context instance by calling the For method with appropriate type parameters. Hence, all lines that follow will have to do with that particular constructor.
Seven lines follow, each declaring one test case. Fail method obviously cover cases in which constructor should throw an exception. Expected exception type is provided, as well as a string which pinpoints the context of the test case. I expect this string to appear later as part of an error message, should the test ever fail.
Calls to the Succeed method obviously cover the successful cases. After specifying the concrete arguments that should be passed to the constructor, I just expect the constructor to execute without throwing any exception. Success cases also contain a string which should be part of the error response in case that test case fails.
Finally, when all seven test cases have been listed, there comes the call to the Assert method.
From this method I expect to effectively walk through the requirements and try the constructor on each of them. Should constructor execution differ from the declared expectation, some error description should be assembled and reported back to the test runner. Fluent interface helps keep the code shorter and easier to read.
This test method is right now not more than a wish list. None of the classes that are required or their methods exist. But bear with me and you will see how simple it is to implement them.
ConstructorTests class will be a static generic class which I am going to put in a dedicated library project:
public static class ConstructorTests<T>
{
}
The first method in this library is the For static method. It should identify the constructor and return a wrapper around it. This wrapper will be the class that I will call Tester. Since it will be located in the specialized namespace which is part of the testing library, I think it is safe to keep the name short.
Here is the implementation of the For method:
public static class ConstructorTests<T>
{
public static Tester<T> For(params Type[] argTypes)
{
ConstructorInfo ctor = typeof(T).GetConstructor(argTypes);
if (ctor == null)
return new MissingCtorTester<T>();
return new CtorTester<T>(ctor);
}
}
Tester class, as well as its descendants MissingCtorTester and CtorTester are still not implemented, but their purpose in the For method should be easy to understand.
MissingCtorTester is a special Tester, which will cover the case when constructor with specified list of arguments is not found in the type which is tested. When the time comes to execute its Assert method, it will grab the opportunity and, instead of running any test cases, it will simply cause the test to fail with message explaining that constructor was not found.
Otherwise, if expected constructor was found in the type under test, we can pass it to CtorTester class, which is specialized to actually test constructor behavior.
Next thing to implement is the Tester base class. It exposes methods Fail and Succeed which have to return Tester again so that next method call can be chained. Finally, Tester exposes the Assert method which is supposed to execute all test cases. Here is the base Tester class:
public abstract class Tester<T>
{
public abstract Tester<T> Fail(object[] args, Type exceptionType, string failMessage);
public abstract Tester<T> Succeed(object[] args, string failMessage);
public abstract void Assert();
}
Implementation of the MissingCtorTester is simpler so I will produce it first:
public class MissingCtorTester<T> : Tester<T>
{
public override Tester<T> Fail(object[] args, Type exceptionType, string failMessage)
{
return this;
}
public override Tester<T> Succeed(object[] args, string failMessage)
{
return this;
}
public override void Assert()
{
Microsoft.VisualStudio.TestTools.UnitTesting.Assert.Fail("Missing constructor.");
}
}
This class is very simple because it has a very simple purpose – to unconditionally fail when Assert is called, simply because the constructor under test is not defined. Note that Fail and Succeed methods are actually just returning this object, which allows the caller to chain more methods, but they don’t do anything. This is because there is nothing to do – the constructor is missing.
Implementation of the CtorTester class is more complicated.
public class CtorTester<T> : Tester<T>
{
private ConstructorInfo constructor;
private IList<TestCase<T> > testCases = new List<TestCase<T> >();
public CtorTester(ConstructorInfo ctor)
{
this.constructor = ctor;
}
public override Tester<T> Fail(object[] args, Type exceptionType, string failMessage)
{
TestCase<T> testCase = new FailTest<T>(this.constructor, args, exceptionType, failMessage);
testCases.Add(testCase);
return this;
}
public override Tester<T> Succeed(object[] args, string failMessage)
{
TestCase<T> testCase = new SuccessTest<T>(this.constructor, args, failMessage);
testCases.Add(testCase);
return this;
}
public override void Assert()
{
List<string> errors = new List<string>();
ExecuteTestCases(errors);
Assert(errors);
}
private void ExecuteTestCases(List<string> errors)
{
foreach (TestCase<T> testCase in this.testCases)
ExecuteTestCase(errors, testCase);
}
private void ExecuteTestCase(List<string> errors, TestCase<T> testCase)
{
string error = testCase.Execute();
if (!string.IsNullOrEmpty(error))
errors.Add(" ----> " + error);
}
private void Assert(List<string> errors)
{
if (errors.Count > 0)
{
string error = string.Format("{0} error(s) occurred:\n{1}",
errors.Count,
string.Join("\n", errors.ToArray()));
Microsoft.VisualStudio.TestTools.UnitTesting.Assert.Fail(error);
}
}
}
This implementation relies on yet another hierarchy of classes. These classes are dealing with actual test cases. They derive from the TestCase base class:
public abstract class TestCase<T>
{
private ConstructorInfo constructor;
private object[] arguments;
private string failMessage;
public TestCase(ConstructorInfo ctor, object[] args, string failMessage)
{
this.constructor = ctor;
this.arguments = args;
this.failMessage = failMessage;
}
protected T InvokeConstructor()
{
try
{
return (T)this.constructor.Invoke(this.arguments);
}
catch (TargetInvocationException ex)
{
throw ex.InnerException;
}
}
protected string Fail(string msg)
{
return string.Format("Test failed ({0}): {1}", this.failMessage, msg);
}
protected string Success()
{
return string.Empty;
}
public abstract string Execute();
}
This class serves as the basic test case. It is capable of calling the constructor and building nice error messages. It also exposes an abstract method Execute, which lets the Tester execute different test cases. Execute method is supposed to return an error message, with empty string meaning that there was no error.
The next task is to implement specific test cases – FailTest and SuccessTest classes:
public class FailTest<T>: TestCase<T>
{
private Type exceptionType;
public FailTest(ConstructorInfo ctor, object[] args, Type exceptionType, string failMessage)
: base(ctor, args, failMessage)
{
this.exceptionType = exceptionType;
}
public override string Execute()
{
try
{
base.InvokeConstructor();
return base.Fail(string.Format("{0} not thrown when expected.",
this.exceptionType.Name));
}
catch (System.Exception ex)
{
if (ex.GetType() != this.exceptionType)
return base.Fail(string.Format("{0} thrown when {1} was expected.",
ex.GetType().Name, this.exceptionType.Name));
}
return base.Success();
}
}
In this implementation the base class will invoke the constructor with arguments that were previously supplied. After that, there are two ways for a test case to fail. Either constructor didn't throw the exception when expected, or it threw some other exception type. In either case, Execute method returns the result which indicates failure. Otherwise, it simply returns success status.
SuccessTest is even simpler:
public class SuccessTest<T> : TestCase<T>
{
public SuccessTest(ConstructorInfo ctor, object[] args, string failMessage)
: base(ctor, args, failMessage)
{
}
public override string Execute()
{
try
{
base.InvokeConstructor();
}
catch (System.Exception ex)
{
return base.Fail(string.Format("{0} occurred: {1}",
ex.GetType().Name, ex.Message));
}
return base.Success();
}
}
The sole purpose of the SuccessTest is to invoke the constructor and catch any exceptions that might be thrown in the process. In case that an exception occurs, method returns failure status. Otherwise, it returns success status.
All the testing code is in place and we can try it. First, I would like to remove the constructor and run the test just to see how it behaves. For that purpose, I will also have to comment out the Draw method test, because it depends on the constructor. If I run the constructor test without the constructor implemented, I receive the "Missing constructor" error as expected:
The next thing that I might try out is to return the constructor back, but to include a couple of defects in it:
public Frame(int length, int width, IDrawingContext context)
{
if (length <= 0) // || width <= 0)
throw new ArgumentException();
//if (context == null)
// throw new ArgumentNullException("context");
this.length = length;
this.width = width;
this.context = context;
}
If I run the tests again, I get the list containing three errors:
As you can see, testing classes have clearly explained which three test cases are failing:
These are the three tests that I have commented out in the constructor and testing classes have isolated them and reported each of them as a separate problem.
Net result of all that I have done is that constructor tests for the Frame class are now fully declarative. They fit one screen, which means that it is much easier to see if some legitimate case is missing from the list. Once again, I must emphasize that I am putting multiple test cases in one test method only when they are naturally tied together to form one comprehensive test.
Now take a moderately large project into account. Most of the classes should contain at least some validation logic. And that means that such classes would have to have a meaningful constructor with validation logic in it. Otherwise, there would be a chance that object is constructed in invalid state. Net result is that in a project with dozens or even hundreds of classes with validating constructors we will have constructor tests that take many thousands of lines of code, with no exaggeration. Using a library such as the one that I've just coded then reduces the effort needed to cover all constructors with tests.
Even more, compact format of the tests that I'm enforcing with this library is very helpful in practice. It helps reduce the chance of omitting an important test case. Testing code that spans multiple lines, and often multiple screens, is harder to track and then it is harder to ensure that nothing is missing in tests.
Good thing about testing libraries is that you write them once in a lifetime. More realistically - a couple of times in a lifetime. But that is far better than writing the same testing code over and over again whenever we write a validating constructor, which normally happens a couple of times a day.
If you wish to learn more, please watch my latest video courses
In this course, you will learn the basic principles of object-oriented programming, and then learn how to apply those principles to construct an operational and correct code using the C# programming language and .NET.
As the course progresses, you will learn such programming concepts as objects, method resolution, polymorphism, object composition, class inheritance, object substitution, etc., but also the basic principles of object-oriented design and even project management, such as abstraction, dependency injection, open-closed principle, tell don't ask principle, the principles of agile software development and many more.
More...
In this course, you will learn how design patterns can be applied to make code better: flexible, short, readable.
You will learn how to decide when and which pattern to apply by formally analyzing the need to flex around specific axis.
More...
This course begins with examination of a realistic application, which is poorly factored and doesn't incorporate design patterns. It is nearly impossible to maintain and develop this application further, due to its poor structure and design.
As demonstration after demonstration will unfold, we will refactor this entire application, fitting many design patterns into place almost without effort. By the end of the course, you will know how code refactoring and design patterns can operate together, and help each other create great design.
More...
In four and a half hours of this course, you will learn how to control design of classes, design of complex algorithms, and how to recognize and implement data structures.
After completing this course, you will know how to develop a large and complex domain model, which you will be able to maintain and extend further. And, not to forget, the model you develop in this way will be correct and free of bugs.
More...
Zoran Horvat is the Principal Consultant at Coding Helmet, speaker and author of 100+ articles, and independent trainer on .NET technology stack. He can often be found speaking at conferences and user groups, promoting object-oriented and functional development style and clean coding practices and techniques that improve longevity of complex business applications.