2016-06-19 1 views
2

Ceci est très étroitement liée à How to use gmock to test that a class calls it's base class' methods mais j'ai du mal à obtenir ce travail avec mon exemple.Comment utiliser GMock vérifier la fonction de classe de base est appelée

J'utilise gtest et GMock pour tester conduire une nouvelle fonctionnalité, donc j'ai une classe de base ...

class SimpleObject 
{ 
public: 
    explicit SimpleObject() {} 

    virtual void moveX(int dX) 
    { 
     // Do important stuff like updating position, bounding box etc. 
    } 

    // ... 
}; 

Sur la base d'autres TDD j'ai une classe dérivée et la nouvelle fonctionnalité est telle que lorsque J'appelle moveX sur l'objet dérivé, il va faire quelque chose de spécifique, mais il doit aussi faire les choses importantes dans SimpleObject :: moveX.

Je possède déjà des tests unitaires pilotés par test liés à la fonction SimpleObject :: moveX, donc je ne veux pas les répéter pour la classe dérivée. Tant que je sais SimpleObject :: moveX est appelé alors tout est Hunky Dory.

Quoi qu'il en soit, basé sur le lien ci-dessus et suivant TDD je me suis retrouvé avec ce qui suit.

La classe dérivée:

class ComplexObject : public SimpleObject 
    { 
    public: 
     virtual void moveX(int dX) 
     { 
      // Do something specific 
     } 
    }; 

La classe 'testables':

class TestableComplexObject : public ComplexObject 
{ 
public: 
    MOCK_METHOD1(moveX, void(int dX)); 

    void doMoveX(int dX) 
    { 
     SimpleObject::moveX(dX); 
    } 
}; 

Le test:

TEST_F(ATestableComplexObject, CallsBaseClassMoveXWhenMoveXIsCalled) 
{ 
    int dX(8); 
    TestableComplexObject obj; 

    EXPECT_CALL(obj, moveX(dX)) 
       .Times(1) 
       .WillRepeatedly(testing::Invoke(&obj, &TestableComplexObject::doMoveX)); 

    obj.moveX(dX); 
} 

Si je lance mes tests, tout passe. Ce n'est pas correct car comme vous pouvez le voir ComplexObject :: moveX ne fait rien. En outre, indépendamment de ce que j'ai mis dans doMoveX (que je pensais était destiné à définir mes attentes) les tests vont encore passer.

Je suis évidemment manquer quelque chose de simple ici, donc des idées?

Répondre

0

Le problème principal ici est que, dans la méthode TestableComplexObject::doMoveX, vous appelez la méthode SimpleObject::moveX. C'est pourquoi tout passe. Vous êtes censé appeler la méthode moveX qui appartient à la classe ComplexObject. Par conséquent, la méthode à changer doMoveX:

void doMoveX(int dX) 
{ 
    ComplexObject::moveX(dX); 
} 

résoudra votre problème.

Il y a un autre problème avec le code que vous avez posté. La dernière déclaration dans le corps de test doit être:

obj.moveX(dX); 

mais je suppose que cela est juste une erreur commise lors de l'écriture à la question?

Espérons que cela aide!

+0

Les tests passent indépendamment de ce que j'ai mis dans doMoveX, d'où vient la confusion. De plus, si j'appelle ComplexObject :: doMoveX alors sûrement cela appellera la fonction dérivée et non la fonction de base dont j'ai besoin pour m'assurer qu'elle est appelée? –

+0

Oui, la dernière déclaration était une faute de frappe. Je vais éditer le message original –

1

Vous devez concevoir votre ComplexObject d'une manière qui vérifie si SimpleObject::moveX a été appelée est possible.Une façon de le faire: encapsule cet appel de base avec une autre fonction qui peut être moqué:

class ComplexObject : public SimpleObject 
{ 
public: 
    virtual void moveX(int dX) 
    { 
     // Call base function 
     baseMoveX(dx); 
     // Do something specific 
    } 
protected: 
    virtual void baseMoveX(int dX) 
    { 
     SimpleObject::moveX(dx); 
    } 
}; 

Ensuite, dans votre classe Testable mock cette fonction de base:

class TestableComplexObject : public ComplexObject 
{ 
public: 
    MOCK_METHOD1(baseMoveX, void(int dX)); 
}; 

Vous ne pouvez pas mock moveX parce que - il n'y a aucun moyen de distinguer entre base et dérivé dans un tel contexte.

Alors - votre test peut ressembler à ceci:

TEST_F(ATestableComplexObject, CallsBaseClassMoveXWhenMoveXIsCalled) 
{ 
    int dX(8); 
    TestableComplexObject obj; 

    EXPECT_CALL(obj, baseMoveX(dX)) 
       .WillOnce(testing::Invoke([&obj] (auto dx) {obj.SimpleObject::moveX(dx); })); 

    obj.moveX(); 
} 

[UPDATE]

Comme il est découvert dans les commentaires - il y a encore un problème de savoir comment assurer ComplexObject: : baseMoveX() appelle SimpleObject :: moveX.

La solution possible est de mettre une classe de plus entre ComplexObject et SimpleObject.

template <typename BaseObject> 
class IntermediateObject : public BaseObject 
{ 
public: 
    virtual void baseMoveX(int dX) 
    { 
     BaseObject::moveX(dx); 
    } 
}; 

avec le test assurant que ce qui se passe réellement:

class TestableBaseMock 
{ 
public: 
    MOCK_METHOD1(moveX, void(int dX)); 
}; 

TEST(IntermediateObject Test, shallCallBaseMoveX) 
{ 
    const int dX = 8; 
    IntermediateObject<TestableBaseMock> objectUnderTest; 
    TestableBaseMock& baseMock = objectUnderTest; 

    EXPECT_CALL(baseMock, moveX(dX)); 

    objectUnderTest.baseMoveX(dx); 
} 

Puis - le mettre entre les classes simples et complexes:

class ComplexObject : public IntermediateObject<SimpleObject> 
{ 
public: 
    virtual void moveX(int dX) 
    { 
     // Call base function 
     baseMoveX(dx); 
     // Do something specific 
    } 
}; 

A la fin - Je veux juste souligner que le changement de votre conception originale - utiliser l'agrégation au lieu de l'héritage (aka motif de conception de décorateur) est le meilleur moyen. Tout d'abord - comme vous pouvez le voir dans ma réponse - essayer de rester avec l'héritage rend le design pire si nous voulons le tester. Deuxième - les tests pour le type de décorateur de conception sont beaucoup plus simples car il a montré dans l'une des réponses ...

+0

Ok cela a du sens et je peux voir comment cela fonctionnerait. Cependant, que se passe-t-il si j'ai plus de fonctions de classe de base dérivées. Est-ce toujours un bon concept acceptable de dupliquer efficacement toutes ces fonctions? –

+0

La mise en œuvre de vos modifications l'amène à SEG faute à la ligne obj.moveX (dX), sans doute parce qu'il est un faux? –

+0

J'ai essayé cet exemple et j'avais ComplexObject :: baseMoveX et ComplexObject :: moveX vides. La première exécution des tests a échoué, donc j'ai mis l'appel baseMoveX dans ComplexObject :: moveX qui est passé. Cela ne devrait pas passer parce que mon ComplexObject :: baseMoveX ne fait rien. Donc, c'est la même situation que j'étais avant –

1

Merci pour les commentaires, avec un tweak à la conception, je suis en mesure de tester ce que je veux.

Tout d'abord, créez une interface pour SimpleObject:

class ISimpleObject 
{ 
public: 
    virtual void moveX(int dX) = 0; 
}; 

Ma classe SimpleObject implémente alors ceci:

class SimpleObject : public ISimpleObject 
{ 
public: 
    explicit SimpleObject() {} 

    virtual void moveX(int dX) 
    { 
     (void) dX; 
     // Do important stuff like updating position, bounding box etc. 
    } 
}; 

Au lieu de ComplexObject héritant de SimpleObject, il hérite de l'interface et est propriétaire d'un SimpleObject (c'est à dire qu'il a un 'plutôt que' est un '). Le constructeur s'assure que nous passons dans un SimpleObject et c'est cette injection qui facilite le moqueur.

class ComplexObject : public ISimpleObject 
{ 
public: 
    ComplexObject(SimpleObject *obj) 
    { 
     _obj = obj; 
    } 

    virtual void moveX(int dX) 
    { 
     _obj->moveX(dX); 
    } 

private: 
    SimpleObject *_obj; 
}; 

Maintenant, je me moque simplement les appels que je suis intéressé par de SimpleObject

class SimpleObjectMock : public SimpleObject 
{ 
public: 
     MOCK_METHOD1(moveX, void(int dX)); 
     // Do important stuff like updating position, bounding box etc. 
}; 

Le test est simplifié trop

TEST_F(AComplexObject, CallsBaseClassMoveXWhenMoveX) 
{ 
    int dX(8); 

    SimpleObjectMock mock; 
    ComplexObject obj(&mock); 

    EXPECT_CALL(mock, moveX(dX)).Times(1); 

    obj.moveX(dX); 
} 

Le comportement est comme prévu. Si la fonction ComplexObject :: moveX est vide (comme ce sera le cas au début), le test échoue. Il ne passera que lorsque vous appellerez SimpleObject :: moveX.