Re: Unit Testing Frameworks (was Re: Singletons)
Tobias M?ller wrote:
Ian Collins <ian-news@this.is.invalid> wrote:
That isn't really a good example. I have a number tests that use a
custom allocator library (mainly for leak checking). All I do is
within a test, tell it the next allocation should fail. Admittedly
these tests are primarily for a C code base where DI isn't an
option, but the technique is still sound.
Ok let me summarize:
You use link time substitution because you don't want to have the
real code around in the test.
Then you realize that your tests need the real code as well. You
cannot use both in parallel, it would cause conflicts.
You resolve that conflict by creating a new mock that can manually
switch between "real" behavior and mock behavior. BTW, how do you
implement the "real" behavior if you cannot use the real code?
I use the run time liker.
The script that generates my mock stubs also produces an alternative
namespace (imaginatively called "real") populated with functions that
call the real library's code. For example (using a C library for
simplicity) in some tests I have for code that manipulates NFSv4
Access Control Lists (ACLs) I find it convenient to use the real
to/from text functions. My generated mock header contains (other
functions removed for brevity):
MOCK_FN_2_DEF( acl_totext, char*, acl_t*, int )
MOCK_FN_2_DEF( acl_fromtext, int, const char*, acl_t** )
Which are macros that produce the actual mock function declarations
and
namespace real
{
struct libsec
{
static void* handle;
static void initialise();
static char* acl_totext( acl_t* p0, int p1 );
static int acl_fromtext( const char* p0, acl_t** p1 );
};
}
The generated mock source contains
MOCK_FN_2_BODY( acl_totext, char*, acl_t*, int )
MOCK_FN_2_BODY( acl_fromtext, int, const char*, acl_t** )
Which are macros that produce the actual mock function definitions and
namespace real
{
void* libsec::handle;
void libsec::initialise()
{
handle = CheckNull(dlopen, ( "libsec.so", RTLD_LAZY ));
};
char* libsec::acl_totext( acl_t* p0, int p1 )
{
typedef char* (*Fn)( acl_t*, int );
static Fn fn = (Fn)CheckNull(dlsym, ( handle, "acl_totext" ));
return fn( p0, p1 );
};
// etc.
}
If I want to call the real function in a test (or test suite), I write
test::acl_fromtext::willCall(real::libsec::acl_fromtext);
test::acl_totext::willCall(real::libsec::acl_totext);
Very simple and the mock generation is fully automated. The real and
mock versions could even both be used in one test, but that would be a
little perverse.
So you rely on the fact, that code that needs the "real" behavior
can always be cleanly separated from code that you want to use the
mock.
As I hope that I've shown, I don't have to.
--
Ian Collins
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]