2008/08/25

Contract Verifiers - Part II. - The Exception Contract Verifier

Designing custom exceptions is not a so easy task. The recommended guidelines are relatively strict; and writing unit tests for them is fastidious. That is why the exception contract verifier is useful. It helps testing your custom exceptions, and catching potential design and behavioral problems.

The contract verifier is available as a companion attribute for your test fixture. It adds the necessary test methods, which verify that your exception is well designed and behaves as expected.
public class MyCustomException : Exception
{
// ...
}
using Gallio.Framework;
using MbUnit.Framework;
using MbUnit.Framework.ContractVerifiers;

[TestFixture]
[VerifyExceptionContract(typeof(MyCustomException))]
public class MyCustomExceptionTest
{
}
Although the test fixture contains no explicit test methods, the test runner will execute 5 tests provided by the contract verifier attribute. Each of them corresponds to a specific guideline:

1. Provides a public default parameter-less constructor.
2. Provides a public constructor taking one single parameter for the error message .
3. Provides a public constructor taking two parameters for the error message and an inner exception.

public class MyCustomException : Exception
{
public MyCustomException()
{
// ...
}

public MyCustomException(string message)
{
// ...
}

public MyCustomException(string message, Exception innerException)
{
// ...
}
}
If for some reason, you decide that your custom exception should handle with construction differently, you can disable the 3 tests above by setting to false the named parameter ImplementsStandardConstructors.
[TestFixture]
[VerifyExceptionContract(typeof(MyCustomException), ImplementsStandardConstructors = false)]
public class MyCustomExceptionTest
{
}
4. Decorates your custom exception with the attribute System.SerializableAttribute.
[Serializable]
public class MyCustomException : Exception
{
}
5. Implements a protected constructor for data serialization.
public class MyCustomException : Exception, ISerializable
{
protected MyCustomException(SerializationInfo info, StreamingContext context)
{
// ...
}
}
Again, in some scenarios, you might decide to not support the serialization process (if your code base targets the compact framework, for instance). Disable the serialization tests by setting to false the named parameter ImplementsSerialization.
[TestFixture]
[VerifyExceptionContract(typeof(MyCustomException), ImplementsSerialization = false)]
public class MyCustomExceptionTest
{
}
When the serialization tests are enabled, the test runner will verify the preservation of data during a complete round trip serialization operation. This will be done for all the constructors; including the 3 recommended standard constructors described above.

The following sample shows the complete code for a general-purpose custom exception; and the test fixture verifying it.
[Serializable]

public class MyCustomException : Exception, ISerializable
{
public MyCustomException()
{
}

public MyCustomException(string message)
: base(message)
{
}

public MyCustomException(string message, Exception innerException)
: base(message, innerException)
{
}

protected MyCustomException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}

public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
}
}
[TestFixture]
[VerifyExceptionContract(typeof(MyCustomException))]
public class MyCustomExceptionTest
{
}
And here is the report generated by Gallio for that test fixture:

No comments: