Wednesday, July 21, 2004

The API Design Guidelines encourage developers to check all their arguments and thereby avoid throwing a NullReferenceException.  If an argument is null and the contract of the method forbids null arguments an ArgumentNullException should be thrown. Brad Adams 

So you agree with Brad (I do) and you always check that arguments are not null before using them. This means a little bit more of code but it is worth it. But this means also a lot more of test code because, ideally, you should test that all your methods check all their arguments. This means writing hundreds of boring, repetitive test cases.... and you don't want to do that.

At least I don't so I added a new feature to MbUnit that does it for me.

Test for ArgumentNullException, first iteration:

 Let's see how it works with an example:

public class ArgumentNullDummyClass
{
    public object ClassicMethod(Object nullable, Object notNullable, int valueType)
    {
        if (notNullable == null)
            throw new ArgumentNullException("notNullable");
        return String.Format("{0}{1}{2}",nullable,notNullable,valueType);
    }
}

As one can see, the nullable parameter can be null, while the notNullable parameter is tested. Now, let's create a fixture that tests this method. We will be using the TestSuiteFixture because we will build a TestSuite:

[TestSuiteFixture]
public class MethodTestSuiteDemo
{
    public delegate object MultiArgumentDelegate(Object o,Object b, int i);
    
    [TestSuite]
    public ITestSuite AutomaticClassicMethodSuite()
    {
        ArgumentNullDummyClass dummy = new ArgumentNullDummyClass();

        MethodTester suite = new MethodTester(
            "ClassicMethod",
            new MultiArgumentDelegate(dummy.ClassicMethod),
            "hello",
            "world",
            1
            );
            suite.AddAllThrowArgumentNull();
        return suite.Suite;
    }
}

The MethodTester class takes the following argument: a name, a delegate  and valid parameters of the delegate. By valid I mean parameters that should not make the delegate invokation fail. The AddAllThrowArgumentNull looks for nullable parameters and create a TestCase that will invoke the delegate with the corresponding parameter nulled. In the example, this means that ClassicMethod will be called with:

  • null, "world", 1
  • "hello", null, 1

Test for ArgumentNullException, second iteration:

There are things I don't like in the example above:

  • you need to create a delegate (tedious),
  • you need to create 1 method tester per method (tedious),

Ok, so let's build a ClassTester class that does that for us... The test code now looks as follows:

[TestSuiteFixture]
public class ClassTesterDemo
{
    [TestSuite]
    public ITestSuite AutomaticClassSuite()
    {
        ArgumentNullDummyClass dummy = new ArgumentNullDummyClass();
        ClassTester suite = new ClassTester("DummyClassTest",dummy);
        suite.Add("ClassicMethod","hello","world",1);
        return suite.Suite;
    }
}

That's much better: delegate is gone and we could add more methods to be tested in a single call.

Test for ArgumentNullException, third iteration:

There is still one problem with this technique: there is no way to tell that an argument is authorized to be nulled! In the example, the nullable parameter can be null and the TestCase will always fail because it does not throw ArgumentNullException.

The solution of this problem is done in two steps: first, you, the developper, tag the parameters that can be nulled with a NullableAttribute attribute (could be any of your attributes). In the example, we add a SmartMethod method and the MyNullableAttribute:

[AttributeUsage(AttributeTargets.Parameter,AllowMultiple=false,Inherited=true)]
public class MyNullableAttribute : Attribute
{}

public class ArgumentNullDummyClass
{
    public object ClassicMethod(Object nullable, Object notNullable, int valueType)
    {...}
    public object SmartMethod([MyNullable]Object nullable, Object notNullable, int valueType)
    {...}
}

Next, you must tell MbUnit which attribute is used to tag nullable parameters. This is done with the NullableAttributeAttribute at the assembly level:

[assembly: NullableAttribute(typeof(MbUnit.Demo.MyNullableAttribute))]

Ok, now we just need to update our test case to load the SmartMethod:

[TestSuite]
public ITestSuite AutomaticClassSuite()
{
    ArgumentNullDummyClass dummy = new ArgumentNullDummyClass();
    ClassTester suite = new ClassTester("DummyClassTest",dummy);

    suite.Add("ClassicMethod","hello","world",1);
    suite.Add("SmartMethod","hello","world",1);

    return suite.Suite;
}

The result in MbUnit GUI is as follows: the parameters of ClassicMethod were all tested, nullable included which we want to avoid. The parameters of SmartMethod were all tested excluded nullable because it was tagged. :)

Test for ArgumentNullException, fourth iteration:

 

The more I think about this problem, the more I think FxCop should do that for us...

posted on Thursday, July 22, 2004 12:37:00 AM UTC  #    Comments [4]
Monday, June 06, 2005 5:47:57 PM UTC
Agreed that FxCop should check it for us!
Darrell
Monday, June 06, 2005 5:47:57 PM UTC
Peli's Blog
Monday, June 06, 2005 5:47:58 PM UTC
Peli's Blog
Monday, June 06, 2005 5:47:58 PM UTC
MbUnit - Unit testing innovation
Mark Levison
Comments are closed.