use (and verify) this with the MediaAccessMock_test
This commit is contained in:
parent
e5e6264493
commit
4b4fcb0c80
3 changed files with 108 additions and 35 deletions
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -41,6 +41,9 @@ namespace test {
|
|||
{
|
||||
MediaDesc& queryFile (string const& name) const;
|
||||
ChanDesc queryChannel (MediaDesc&, uint chanNo) const;
|
||||
|
||||
public:
|
||||
typedef MediaAccessFacade ServiceInterface;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue