次の方法で共有


Go Ahead, Mock Me

So you have a UI or component or something that you want to test, but you don't really want all the complexity of driving it plus figuring out how to reliably verify its actions via the kernel or other component or whathaveyou it affects.  You need a Mock Object.

What's a Mock Object?  Something that pretends to be your app's kernel (or, more generally, any component used by any other component) but really is just a shell keeping track of what the UI (or whatever component is driving it) tells it and responding however you tell it to.  You can find lots of information about Mock Objects online (get started at https://c2.com/cgi/wiki?MockObject and https://www.martinfowler.com/articles/mocksArentStubs.html), but the basic idea is that your kernel or component or whatever has an interface that is all your UI or other component or whatever knows about:

public class Kernel : IKernel // or some base class
{
    // Non-public constructor because this object is 
    // created by a factory somewhere, not by the caller.
    internal Kernel(); 
    public property SomeSubsystem { get; }
    public void SaveEverything();
    public SomeObject CreateSomething(string id);
}

The Mock Object exposes exactly the same interface, plus one extra method:

public class MockKernel : IKernel // or some base class.
{
    // Non-public constructor because this object is
    // created by a factory somewhere, not by the caller.
    internal MockKernel();
    public property SomeSubsystem { get; }
    public void SaveEverything();
    public SomeObject CreateSomething(string id);

    static void Initialize(stream executionInformation);
}

Clearly this only works if your UI doesn't create the kernel directly (which you would never do, right?)  If you have a KernelFactory somewhere, you configure it to create the MockKernel instead.  If Kernel is a COM object, MockKernel registers itself using the same GUIDs as Kernel does, and come CoCreate time MockKernel gets instantiated rather than Kernel.

Verifying your UI or component or whatever does the right thing is now simple, because when it calls the SomeSubsystem property or the SaveEverything method or the CreateSomething method you *know*, and you can easily verify that it passes the correct arguments.  No more trying to decipher application internal state or attempting to detangle the action from its various side effects.

Of course, your Mock Object is responsible for creating whatever data or objects need to be handed back.  This is where that Initialize method comes in.  When your test case initializes your Mock Object it gives it a script that tells it what to do (I find XML works nicely for this).  This works especially well if the component generates data of some sort, as that's easy to specify in the script.  If the Mock Object needs to create actual objects, as in this example, the script can list the data necessary to create a Mock Object of that type (which data could just be a pointer to another configuration file).

Alternatively, your Mock Object can simply note that the method was called and then forward the call to the actual Kernel and let it do the heavy lifting.  When that call returns your Mock can still mess with the return value.  Your Mock could also decide to not call the Kernel at all but instead just make up data, return null, or throw some random exception.  This provides all sorts of interesting avenues for coming up with nasty test scenarios.

Mock Objects are especially useful when you want to test error handling.  You could come up with some scheme for eating up all available RAM or for filling up the hard drive, or you could just tell your Mock Object to throw an OutOfMemoryException on the third call to SomeSubsystem and to throw various I/O errors on every other call to CreateSomething.

Mocks not enough for you?  As useful as they are, Mock Objects are just one specific type of test hook.  I'll talk more about test hooks in general in a future post.

*** Comments, questions, feedback?   Want a fun job on a great team?  Send two coding samples and an explanation of why you chose them, and of course your resume, to me at michhu at microsoft dot com.  I need a tester and my team needs a data binding developer, program managers, and a product manager.  Great coding skills required for all positions.

Comments