Content

Not quite a Yegge long.

Mock Objects for C++, Part 4

Wednesday 5 November 2008 - Filed under Code

This is the fourth installment in an ongoing series on building support for mock objects in C++. If you’d rather just use the features, grab a tested library for Visual Studio 2005/2008 here.

Last time, we looked at how the expectation system hangs together, and I finished off by saying that we would recover function signature information from an inferred type parameter.

Let’s have a closer look at what’s going on there.

Suppose I have the following interface and expectation:

class _declspec(novtable) IFoo
{
public:
    virtual int _cdecl Bar( float baz ) = 0;
};

// sometime later…
expect( foo, &IFoo::Bar );

Now when we call expect(), the compiler infers two type parameters, as if we had written:

expect< IFoo, int (_cdecl IFoo::* )( float ) >
    ( foo, &IFoo::Bar );

Those two types get bound to I and F, respectively, in the expect() template. There’s a slight problem here, in that we really need to be able to pull F apart into a usable signature – we want to know the return type, the number of arguments and their types, etc.

Since we don’t have any kind of reflection capability, this looks difficult at a first glance. However, we can use template specialization to solve this.

First, we’ll define a really boring primary template for a function signature:

template< typename F >
struct FunctionArgs {};

Now for each number of arguments we want to support, we’ll define a specialization:

template< typename T, typename I >
struct FunctionArgs< T (_cdecl I::*)( void ) >
{
    typedef typename void_hack<T>::type Result;
    typedef ERROR_WRONG_NUMBER_OF_ARGS Arg0;
    typedef ERROR_WRONG_NUMBER_OF_ARGS Arg1;
    // etc etc
};

template< typename T, typename I, typename A0 >
struct FunctionArgs< T (_cdecl I::*)( A0 ) >
{
    typedef typename void_hack<T>::type Result;
    typedef A0 Arg0;
    typedef ERROR_WRONG_NUMBER_OF_ARGS Arg1;
    // etc etc
};

We’re using a couple of helpers here which I hadn’t introduced yet. They exist purely to get around errors later on when we would end up trying to create a value of type void. If you wanted this to work in VC6, you’d need a few more hacks. Here’s what I used, anyway:

struct ERROR_WRONG_NUMBER_OF_ARGS
{
    bool operator != (
        ERROR_WRONG_NUMBER_OF_ARGS e ) const
    {
        assert( !"Should never get here!" );
        return false;
    }
};

The operator != overload is required so we can do arg checking against args that don’t exist, without requiring special-case code (which in this case, involves a massive pile of templates)

There’s a similar placeholder type: FUNCTION_RETURNS_VOID, with the same behavior – if you manage to actually call operator !=, you get an assertion failure, since it’s not meant to happen.

Then void_hack<T> converts a possibly-void return type to something we can create a variable of. If we don’t do this, we end up running into all kinds of trouble, and at least doubling the number of FunctionArgs specializations we need. void_hack<T> itself uses specialization again. Here’s the primary template, which works for all T except void:

template< typename T >
struct void_hack { typedef T type; };

And in the void case:

template<>
struct void_hack<void >
{ typedef FUNCTION_RETURNS_VOID type; };

Then, regardless of T, we can do this (not part of the real code!):

template< typename T >
void Foo()
{
    void_hack<T>::type bar;    // not an error
}

Before we can write withArgs(), we need one more helper – a wrapper somewhat analogous to Haskell’s Maybe type, or C#’s `T?`type:

struct argempty_t {};

template< typename T >
struct arg_t
{
    T t;   // later: fix this for references!
    bool isEmpty;

    arg_t( T const & t ) : t(t), isEmpty(false) {}
    arg_t( argempty_t ) : t(), isEmpty(true) {}
};

static argempty_t _;  // syntactic hack

Now, to actually use this:

template< typename I, typename F >
class Expectation
{
    // *snip* fields, ctor, etc

    Expectation withArgs(
        typename FunctionArgs<F>::Arg0 a0 = _,
        typename FunctionArgs<F>::Arg1 a1 = _,
        ) // more args snipped
    {
        e->filter = new ArgFilter<
            typename FunctionArgs<F>::Arg0,
            typename FunctionArgs<F>::Arg1
            > //snip
            ( a0, a1, /* snip */ );

        return *this;
    }
}

This is slightly simplified from what the actual library does (we need to bind the type of the return value, in order to get a usable stack offset, etc).

Next time, we’ll look into how we do the checking of actual arguments against the filter. Yes, it’s another massive hack.

2008-11-05  »  admin

Talkback x 6

  1. Chris on Software » Blog Archive » Mock Objects for C++, Part 5
    15 November 2008 @ 6:39 pm

    [...] Last time, we looked at how to recover argument and return types from the function signature, via a pile of templates. [...]

  2. Kirk Korver
    21 November 2008 @ 7:26 am

    This just looks like a reimplementation of the amop. http://code.google.com/p/amop/. This is a good explanation of how this technique works.

  3. admin
    21 November 2008 @ 7:52 am

    Yes, AMOP is doing a similar thing, and looks nice. Their syntax is a bit on the ugly side though ;)

  4. admin
    21 November 2008 @ 7:53 am

    Or rather, their syntax deviates more than necessary from other Mock Object libraries (for Java, .NET, etc)

  5. Peter Bindels
    24 December 2008 @ 11:06 pm

    This looks an *awful* lot like my mocking framework – including just about all the syntax, even.

    My selling point is that it all is in one header file so you have no library logic or include paths to worry about. Also, the part after this (argument matching) is very neatly implemented with true objects and operator== calls for the classes instead of a hack as you’re insinuating.

    Maybe worth a look to see if we can merge the code bases? I have no support whatsoever for repetition mentioning since I don’t see the value of it over looping on a single expectation (except for optimization – which I don’t do in my unit tests).

  6. admin
    25 December 2008 @ 9:55 am

    Peter:

    Sounds like a plan. I’m not surprised there are better ways to do this than what I’m doing – a few of us just hacked this together because we needed something that worked :)

Share your thoughts

Re: Mock Objects for C++, Part 4







Tags you can use (optional):
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>