8.5. Delegating the Test to Reflection

In the introduction to the delegate type in Section 2.12, there is a brief discussion of a testHarness class. testHarness maintains a static delegate member in which other classes register test functions they wish executed. It looks like this:

public delegate void Action();
public class testHarness
{
    static private Action theAction;
    static public Action Tester
         { get{ return theAction;  }
           set{ theAction = value; }}
    static private void reSet() { theAction = null; }
    static public  int  count()
         { return theAction != null
                ? theAction.GetInvocationList().Length : 0; }
    // ...
}

By convention, a class wishing to register one or more member functions with Tester does so within the static constructor of the class—for example,

public class testHashtable
{
    public void test0(){ ... }
    public void test1(){ ... }

    static testHashtable()
    {
        testHarness.Tester += new testHarness.Action( test0 );
        testHarness.Tester += new testHarness.Action( test1 );
    }

    // ...
}

The program entry point invokes the testHarness static run() member function. run() tests whether the delegate addresses any functions; if it does, run() executes them and then resets the delegate:

public class EntryPoint
{
    public static void Main(){ testHarness.run(); }
    // ...
}

public class testHarness
{
    public static void run()
    {
        if ( Tester != null )
           { Tester(); reset(); }
    }
    // ...
}

There is one piece missing, however, in order for this to work. Our strategy depends on the invocation of the static constructor of each class wishing to register its test methods. Moreover, these constructors must be invoked before run() executes. Because run() is the first statement of the Main() entry point, we don't have much working space to get this done. So there are two questions we need to answer:

  1. Where can we get the work done, in order to ensure that run() is invoked prior to the first statement of Main()?

  2. What work, exactly, needs to be done to guarantee the invocation of the appropriate static constructors?

There is only one absolutely certain place that we can locate the work we need to get done. We are guaranteed that the testHarness static constructor is invoked before run() is evaluated within Main(). This is exactly the time when our work needs to be done. This is where we place it.

What does the work consist of? First we need to discover all the types within the application. These classes potentially have test methods they wish to register.

Next we need to trigger the execution of the type's static constructor. We do this by creating an instance of the type. As an optimization, we check if the type is part of the predefined .NET framework, and if so, we do not create an instance.

This will take some time, of course, but we're testing, so time is not terribly critical. Moreover, we save staff time by automating the test process.

To discover the types present in an executable, we first retrieve the assemblies associated with the application—for example,

AppDomain appdomain    = AppDomain.CurrentDomain;
Assembly [] assemblies = appdomain.GetAssemblies();

GetAssemblies() returns an array of Assembly class objects that have been loaded into the application. We can then filter out the System assemblies:

foreach ( Assembly a in assemblies )
{
      if ( isSystemAssembly( a ))
           continue;

The remaining assemblies may contain classes that wish to register test functions to be executed. For each assembly we invoke GetTypes() to retrieve an array of the Type objects contained within the assembly. We then iterate across and create an instance of the types:

   Type [] t = a.GetTypes();

   foreach ( Type tt in t )
         if ( tt.isClass )
               Activator.CreateInstance( tt );
}

The CreateInstance() Iinvocation triggers the invocation of the associated static constructor of the class, if defined, which in turn populates the test.Tester delegate before test.run() executes.

There are other design strategies, of course. We could have the class register its name, or pass in its type, or somehow make itself known to testHarness. I wanted to try something in which the classes themselves are passive. Their mere presence seems to magically cause instances to be created and the tests to be executed.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.189.171.153