wrote a very interesting post about terse vs concise coding. He demonstrated the idea by using an example of unit test. You can check out this post here.
The point is that although we can use fancy attributes to decorate our unit test methods and cut down on writing more code to satisfy the condition but we make our unit tests terser instead of making it clearer.
Let’s take a small example. Consider we are building a Stack class called “SimpleStack” using Test Driven Development. The SimpleStack class must have a size less than 10 and it cannot add null items.
We start out with the following unit test.
[TestFixture]
public class when_a_stack_is_initialized
{
[Test]
[ExpectedException(typeof(Exception),"Stack Max should be less than 10")]
public void should_throw_an_exception_if_the_stack_max_size_is_greater_than_10()
{
SimpleStack stack = new SimpleStack(11);
}
[Test]
public void make_sure_the_stack_is_empty()
{
SimpleStack stack = new SimpleStack();
Assert.IsTrue(stack.Count == 0);
}
}
As, you can see from the above code that the unit test “should_throw_an_exception_if_the_stack_max_size_is_greater_then_10” is suppose to throw an exception when the test fails. This is performed by decorating the unit test method with the “ExpectedException” attribute.
Here is the implementation of the SimpleStack class which ensures that the unit test is passed.
public SimpleStack(int maxSize)
{
MaxSize = maxSize;
}
public int MaxSize
{
get { return _maxSize; }
set
{
_maxSize = value;
if (_maxSize > 10)
throw new Exception("Stack Max should be less than 10");
}
}
Now, if you run the above test it will pass. Let’s move to the next feature which is that the SimpleStack should throw an exception when the null item is inserted.
[TestFixture]
public class when_an_item_is_inserted_into_stack
{
[Test]
[ExpectedException(typeof(Exception)]
public void should_throw_an_exception_if_the_item_is_null()
{
SimpleStack stack = new SimpleStack(12);
stack.Add(null);
}
}
And here is the “Add” method of the SimpleStack class.
public virtual void Add(object obj)
{
if (obj == null)
throw new Exception("Null item cannot be inserted into stack");
}
Now, run the above test and it will pass. But, it passed for the wrong reasons. The line stack.Add() never got executed and still the unit test passed. This is because the following line threw and error making the test pass.
SimpleStack stack = new SimpleStack(12); // max size should be less than 10
This is an interesting problem. We certainly don’t want our test to pass for the wrong reasons. There are several ways to fix this problem. You can make use of the ExpectedException message feature to compare the correct message of the thrown exception.
public virtual void Add(object obj)
{
if (obj == null)
throw new Exception("Null item cannot be inserted into stack");
}
[Test]
[ExpectedException(typeof(Exception), "Null item cannot be inserted into stack")]
public void should_throw_an_exception_if_the_item_is_null()
{
SimpleStack stack = new SimpleStack(12);
stack.Add(null);
}
Now, the above test will fail because the message received from the exception is not the same message as expected. This is a hack approach since it requires you to write messages when throwing exceptions.
Another approach is to check the type of the exception thrown. But this approach is also not practical since it requires the developers to create different custom exceptions for all the expected exceptions.
The final way as suggested by Venkat is to use the Try-Catch block and make sure the exception is thrown when expected. Here is the above example using the Try-Catch blocks.
[Test]
[ExpectedException(typeof(Exception))]
public void should_throw_an_exception_if_the_item_is_null()
{
SimpleStack stack = new SimpleStack(12);
try
{
stack.Add(null);
}
catch(Exception ex)
{
Assert.IsTrue(true);
}
}
As, you can see that I have removed the message criteria from the ExpectedException attribute and introduced a Try-Catch block. The above test will FAIL because the exception is not thrown when expected. To make the test pass simple initialize the SimpleStack object with a max size less than 10.