use (and verify) this with the MediaAccessMock_test

This commit is contained in:
Fischlurch 2011-05-20 02:02:14 +02:00
parent e5e6264493
commit 4b4fcb0c80
3 changed files with 108 additions and 35 deletions

View file

@ -20,6 +20,40 @@
*/
/** @file mockinjector.hpp
** Support for unit-testing with a mock implementation of some singleton service.
** Using a Singleton to access a global service (instead of using a dependency injection
** framework) has the downside of making unit-tests hard to write. When the code to be
** tested accesses the global service, usually, several further services must be available.
** Usually this (and the lack of flexibility) counts as the main arguments against using
** Singletons. Yet still our current analysis (as of 2011) for Lumiera is that this
** drawback doesn't justify all the overhead of writing our own dependency-injection
** container (given the lack of pre-existing suitable C++ implementations).
**
** lib::test::MockInjector provides a mechanism to overcome this problem. Given
** a Singleton service, it allows to install a subclass of the service interface
** temporarily as a mock implementation. As an additional convenience shortcut,
** the Use4Test template can be used as a scoped variable to automate this process
** of installing and removing the mock service within the call scope of a single test
** method or test object.
**
** \par prerequisites
** The Singleton needs to be managed and accessed through Lumiera's lib::Singleton
** factory. Moreover, an explicit template specialisation to use MockInjector instead
** of the plain lib::SingletonFactory needs to be given globally, for the service interface
** to use. For this template specialisation, a forward declaration of the service is
** sufficient. See singleton-preconfigure.hpp for examples.
**
** The Mock implementation of the service needs to be default constructible and
** assignment compatible to the service interface. Moreover, the mock implementation class
** must expose a typedef \c ServiceInterface pointing to this interface. The instance of
** the mock service implementation will be created in heap memory and managed by a static
** smart-ptr variable; it will be destroyed when leaving the scope marked by Use4Test.
**
** @see SingletonFactory
** @see SingletonTestMock_test
** @see MediaAccessMock_test usage example
*/
#ifndef LUMIERA_TEST_MOCKINJECTOR_H
@ -31,10 +65,15 @@
#include <boost/scoped_ptr.hpp>
#include <boost/noncopyable.hpp>
#include <boost/static_assert.hpp>
#include <boost/utility/enable_if.hpp>
namespace lib {
template <class SI>
class Singleton; // forward decl; see singleton-preconfigure
namespace test{
using boost::scoped_ptr;
@ -49,7 +88,7 @@ namespace test{
template<class SI>
class MockInjector : public SingletonFactory<SI>
{
scoped_ptr<SI> mock_;
static scoped_ptr<SI> mock_;
public:
/** Overwriting the normal Singleton creation Interface
@ -74,32 +113,59 @@ namespace test{
MockInjector () {};
/** @note MockInjector singleton factory objects can be copied,
* but the copy will start out with clean internal state,
* i.e. exhibiting normal SingletonFactory behaviour
* without mock object.
*/
MockInjector (const MockInjector& other)
: SingletonFactory<SI>(other), mock_(0) { }
MockInjector<SI>& operator= (const MockInjector<SI>& other)
{
return SingletonFactory<SI>::operator= (other);
}
// MockInjector objects may be copied freely (no object fields)
};
/** embedded static var to hold onto a configured mock implementation */
template<class SI>
scoped_ptr<SI> MockInjector<SI>::mock_;
/**
* Details of creating and accessing mock instances,
* especially for services implemented as Singleton.
*/
/** Details of creating and accessing mock instances, *
* especially for services implemented as Singleton. */
namespace mock {
using boost::enable_if;
using lumiera::Yes_t;
using lumiera::No_t;
/**
* Metafunction: does the Type in question
* give us a clue about what service interface to use?
*/
template<class MOCK>
class defines_ServiceInterface
{
META_DETECT_NESTED (ServiceInterface);
public:
enum{ value = HasNested_ServiceInterface<MOCK>::value
};
};
/**
* Trait template: getting the types of the
* Service interface and SingletonFactory
*/
template<class IMP, class YES =void>
struct SingletonAccess
{
typedef No_t ServiceInterface;
typedef No_t Factory;
};
template<class IMP>
struct SingletonAccess<IMP, typename enable_if< defines_ServiceInterface<IMP> >::type>
{
typedef typename IMP::ServiceInterface ServiceInterface;
typedef lib::Singleton<ServiceInterface> Factory;
};
/**
* A Metafunction to find out, if a type in question
* is used as Singleton and especially has been configured
@ -108,16 +174,14 @@ namespace test{
template<class MOCK>
class is_Singleton_with_MockInjector
{
META_DETECT_NESTED (ServiceInterface);
META_DETECT_FUNCTION(void, injectSubclass, (TY*) const);
typedef typename MOCK::ServiceInterface ServiceInterface;
typedef typename SingletonAccess<MOCK>::ServiceInterface Interface;
typedef typename SingletonAccess<MOCK>::Factory SingletonFactory;
META_DETECT_MEMBER (injectSubclass);
public:
typedef lib::SingletonFactory<ServiceInterface> SingletonFactory;
enum{ value = HasNested_ServiceInterface<MOCK>::value
&& HasFunSig_injectSubclass<SingletonFactory>::value
enum{ value = defines_ServiceInterface<MOCK>::value
&& HasMember_injectSubclass<SingletonFactory>::value
};
};
@ -127,19 +191,24 @@ namespace test{
* a mock service implementation?
*/
template<class MOCK, class YES =void>
struct AccessPoint;
struct AccessPoint
{
BOOST_STATIC_ASSERT (is_Singleton_with_MockInjector<MOCK>::value );
}; // Hint: (1) the mock needs a typedef pointing to the ServiceInterface
// (2) prepare the Singleton-Factory for injecting a Mock implementation...
// look into singleton-preconfigure.hpp for examples!
template<class MOCK>
struct AccessPoint<MOCK, typename enable_if< is_Singleton_with_MockInjector<MOCK> >::type>
{
typedef typename is_Singleton_with_MockInjector<MOCK>::SingletonFactory ServiceAccessPoint;
typedef typename SingletonAccess<MOCK>::Factory ServiceAccessPoint;
void
static void
activateMock()
{
ServiceAccessPoint().injectSubclass (new MOCK());
}
void
static void
deactivateMock()
{
ServiceAccessPoint().injectSubclass (0);
@ -155,19 +224,21 @@ namespace test{
* Placing a suitably specialised instance of this template into a local scope
* will inject the corresponding mock installation and remove it when the
* control flow leaves this scope.
* @param MOCK the concrete mock implementation to inject. It needs to
* subclass the service interface and expose a typedef \c ServiceInterace
*/
template<class SI>
template<class MOCK>
struct Use4Test
: boost::noncopyable
{
Use4Test()
{
mock::AccessPoint<SI>::activateMock();
mock::AccessPoint<MOCK>::activateMock();
}
~Use4Test()
{
mock::AccessPoint<SI>::deactivateMock();
mock::AccessPoint<MOCK>::deactivateMock();
}
};

View file

@ -41,6 +41,9 @@ namespace test {
{
MediaDesc& queryFile (string const& name) const;
ChanDesc queryChannel (MediaDesc&, uint chanNo) const;
public:
typedef MediaAccessFacade ServiceInterface;
};

View file

@ -30,6 +30,7 @@
#include <iostream>
using lib::test::Use4Test;
using lib::Literal;
using std::string;
using std::cout;
@ -52,12 +53,10 @@ namespace test {
virtual void run(Arg)
{
MAF::instance.injectSubclass (new MediaAccessMock);
Use4Test<MediaAccessMock> within_this_scope;
queryScenario ("test-1");
queryScenario ("test-2");
MAF::instance.injectSubclass (0);
}