I've been working on a project which is little more than an abstract class and a bunch of test cases to verify that the outsourced implementers of this class make appropriate parameter validation on their overridden methods. I thought, "man, it sure would be nice if I could just put a code contract on my abstract class and then I wouldn't have to write all of these annoying unit tests."
Well, I just couldn't wait, so again I leveraged the pre-compiler flexibility of PostSharp and AoP concepts to simplify things a little (at least until code contracts are widely supported).
The first thing I did was define the contract for my parameter validation attributes:
[AttributeUsage(AttributeTargets.Parameter)]Then, I wrote a method attribute with a method boundary aspect to process the parameter attributes:
public abstract class ParameterAttribute : Attribute
{
public abstract void CheckParameter(ParameterInfo parameter, object value);
}
[Serializable]The version I wrote in honor of my friend Sir Nathan Rigsby is called CanHazParameterAttributes.
public class HasParameterAttributes : OnMethodBoundaryAspect
{
public override void OnEntry(MethodExecutionEventArgs eventArgs)
{
ParameterInfo[] pis = eventArgs.Method.GetParameters();
Object[] args = eventArgs.GetReadOnlyArgumentArray();
for (int i = 0; i < pis.Length; i++)
foreach (ParameterAttribute pa in pis[i].GetAttributes<ParameterAttribute>())
pa.CheckParameter(pis[i], args[i]);
base.OnEntry(eventArgs);
}
}
The HasParameterAttributes method attribute implementation uses an extension method I wrote to get a specific type of attributes from the PropertyInfo objects. You can find it in the article entitled: Extension Method to Get Custom Attributes with Reflection
I wrote two basic parameter validators to test the system:
public class NotNull : ParameterAttributeThe test I wrote does not execute like you would expect a unit test to execute. That is because I didn't have a lot of time to spend on this so I was too lazy to specify which test cases expect exceptions and which don't. If you run these test cases, you'll see that the ones which throw exceptions fail and the ones which don't throw exceptions pass. They perform as designed. Here's my NUnit test:
{
public string Message { get; set; }
public override void CheckParameter(ParameterInfo parameter, object value)
{
if (value == null)
throw new ArgumentNullException(parameter.Name, Message);
}
}
public class NotEmpty : ParameterAttribute
{
public string Message { get; set; }
public override void CheckParameter(ParameterInfo parameter, object value)
{
if (value != null && value.ToString().Length == 0)
throw new ArgumentException(Message, parameter.Name);
}
}
[TestFixture]
public class Tests
{
[Test, Combinatorial]
[HasParameterAttributes]
public void TestNotNull
([NotNull, Values(null, "", "Lulu")] string param1,
[NotEmpty, Values(null, "", "Lulu", 0)] object param2)
{ }
}
This comment has been removed by the author.
ReplyDeleteHi Patrick,
ReplyDeleteGreat article. It really got me thinking more carefully on how to solve all my validation woes. Was wondering if you had seen this post from the creator of PostSharp that kind of goes along the same lines (found here) and is to do with design by contract. Unfortunately the google code site for it is a mess but the correct place to find the source code is here.
Ed
Nice!!
ReplyDeleteone nice side effect is that it should be very easy (search and replace?!) to switch to MS CodeContracts when this becomes ready to use,
which would offer you compile time checks for your input parameters.
http://research.microsoft.com/en-us/projects/contracts/
This comment has been removed by the author.
ReplyDelete