A "brief" report on using Google
Mock to
mock Ogre. (Sorry it's taken a while)
In summary: It's possible, fairly readable and I think worthwhile. It can be slightly painful to setup initially but you only need to define your Adapter and
Mock classes once, and only the bits of the interface you're currently using.
First the aims:
1) Be able to run tests without any Ogre initialisation.
(once you start to initialise the real system, you quickly need some kind of renderer etc.)
2) Being able to create and pass any mocked Ogre object (such as an Entity) to the code under test.
(Using raw Ogre, this can be difficult due to non-public constructors and dependency on resources)
3) For a "final / deployment" build, be able to compile away any virtual calls that are only virtual for testing purposes.
4 Don't modify Ogre's own source code.
I defined an "Adapter" for every non-trivial Ogre class involved in tested code.
The adapter interface appears identical to the real Ogre interface from the app's point of view, except
a) every method is pure virtual (even those that are non-virtual in Ogre). This is required to be able to
mock it (specifically to set expectations on the method).
b) Method parameters and return types need to be changed to the adapter version where applicable.
E.g. for SceneNode
Code: Select all
namespace OgreAdapters
{
class SceneNode : public Node
{
public:
virtual ~SceneNode() {}
virtual void attachObject( MovableObject* obj) = 0;
virtual SceneNode* createChildSceneNode(
const Ogre::Vector3& translate = Ogre::Vector3::ZERO,
const Ogre::Quaternion& rotate = Ogre::Quaternion::IDENTITY ) = 0;
virtual SceneManager* getCreator(void) const = 0;
...
};
}
(note Node, MovableObject and SceneManager in above types refers to OgreAdapters type)
Then derive a
mock class from this with Google
Mock:
Code: Select all
namespace OgreMocks
{
class MockSceneNode : public OgreAdapters::SceneNode
{
public:
MOCK_METHOD1( attachObject, void ( OgreAdapters::MovableObject* obj) );
MOCK_METHOD2( createChildSceneNode, SceneNode* ( const Ogre::Vector3& translate, const Ogre::Quaternion& rotate ) );
MOCK_CONST_METHOD0( getCreator, OgreAdapters::SceneManager* (void) );
...
};
}
With this, a fairly simple test
Code: Select all
TEST_F( AxisGridNodeBuilderFixture, CreatesXAxisEntity )
{
SetupNodeReturnSequence();
SetupEntityReturnSequence();
EXPECT_CALL( m_axisNodes[0], attachObject( &m_axisEntities[0] ) );
EXPECT_CALL( m_axisNodes[0], setDirection( Vector3::UNIT_X, Ogre::Node::TS_LOCAL, _ ) );
EXPECT_CALL( m_axisNodes[0], setScale( Vector3( kLateralScale, kLateralScale, kLengthScale ) ) );
EXPECT_CALL( m_axisEntities[0], setMaterialName( xMaterial ) );
m_builder.BuildNode( m_parentNode, kOffset );
}
Which uses this fixture:
Code: Select all
class AxisGridNodeBuilderFixture : public testing::Test
{
public:
AxisGridNodeBuilderFixture()
{
EXPECT_CALL( m_parentNode, getCreator() ).WillRepeatedly( Return( &m_sceneManager ) );
m_builder.SetAxisMeshName( kMeshName );
m_builder.SetMaterialForAxis( xMaterial, QSim::XAxis );
...
}
protected:
// Simpler if we assume a certain order: X,Y,Z
void SetupNodeReturnSequence()
{
EXPECT_CALL( m_parentNode, createChildSceneNode( kOffset, _ ) )
.Times( AtMost(3) )
.WillOnce( Return( &m_axisNodes[0] ) )
.WillOnce( Return( &m_axisNodes[1] ) )
.WillOnce( Return( &m_axisNodes[2] ) );
}
void SetupEntityReturnSequence()
{
EXPECT_CALL( m_sceneManager, createEntity( _, kMeshName ) )
.Times( AtMost(3) )
.WillOnce( Return( &m_axisEntities[0] ) )
.WillOnce( Return( &m_axisEntities[1] ) )
.WillOnce( Return( &m_axisEntities[2] ) );
}
QSim::AxisGridNodeBuilder m_builder;
OgreMocks::MockSceneNode m_parentNode;
OgreMocks::MockSceneManager m_sceneManager;
OgreMocks::MockMeshManager m_meshManager;
// NiceMock eliminates warnings about "uninteresting calls",
// i.e. calls which we didn't set expectation for as we don't care
testing::NiceMock< OgreMocks::MockSceneNode > m_axisNodes[3];
OgreMocks::MockEntity m_axisEntities[3];
};
}
The above satisfies aims 1 & 2. We don't even need to create an Ogre::Root to run the tests.
One big assumption: You're willing to setup a diferent build config to build and run your tests, vs building your actual app. If you want to use the same build config, you'll need "forwarding" subclasses of the adapter classes that forward calls on to the real Ogre object when running your app.
For aim #3:
The code under test always uses the OgreAdapters type.
But a preprocessor flag can resolve this back to the raw ogre type:
Code: Select all
namespace OgreAdapters
{
#ifdef ENABLE_OGRE_ADAPTERS
#define DECLARE_OGRE_ADAPTER( className ) class className;
#else
#define DECLARE_OGRE_ADAPTER( className ) using Ogre::className;
#endif // ENABLE_OGRE_ADAPTERS
DECLARE_OGRE_ADAPTER( Entity );
DECLARE_OGRE_ADAPTER( SceneNode );
...
Some other details:
* For "trivial" Ogre classes (those that don't depend on other classes / resources and are easy to inspect) I just use the Ogre class directly rather than mocking it. E.g. Vector3
* Any
mock of a singleton should implement the GetSingleton* methods for real
* Adapting a shared pointer requires something like
Code: Select all
namespace OgreAdapters
{
class Mesh
{
public:
...
};
class MeshPtr : public Ogre::SharedPtr< Mesh >
{
public:
// other constructors not yet implemented
MeshPtr( Mesh* rep ) : SharedPtr( rep ) {}
};
}
At some point in you app code, where you move from untested code to tested code you need to start using the OgreAdapters type instead of the raw Ogre type.
I make this switch using an AdapterManager:
Code: Select all
namespace OgreAdapters
{
#ifdef ENABLE_OGRE_ADAPTERS
class AdapterManager
{
public:
static SceneManager* Adapt( Ogre::SceneManager* ogreObject );
...
// Not yet implemented: will only be needed if a testable build actually uses Adapt()
static void DestroyAdapter( SceneManager* adapter );
...
};
#else //==> !ENABLE_OGRE_ADAPTERS
class AdapterManager
{
public:
static inline SceneManager* Adapt( Ogre::SceneManager* ogreObject ){ return ogreObject; }
...
// There was no separate adapter created, so nothing to destroy
static inline void DestroyAdapter( SceneManager* adapter ) {}
...
(This is where you would have to return a "forwarding" version of the adapters if you choose that path)
If you want to look at the full code, it's available on the sourceforge project:
http://qsimulator.svn.sourceforge.net/v ... main/code/
(Revision 53 is known working build at present)
There are separate VS 2005 projects for adapters, mocks, tests. To build it requires a bit more setup, see the "rough" dev guide in the parallel "docs" folder. Basically requires Ogre, CEGUI, Google
Mock (and its boost tr1/tuple dependency) and Google Test.