2015-03-24 1 views
0

Disons que j'écris une classe de voiture. Il devrait avoir les méthodes configEngine et currentGasolineConsumption à côté d'autres méthodes. Donc, je refactorisé le calcul de la consommation d'essence dans un polymorphisme de classe du moteur et de l'utilisation pour obtenir la consommation d'essence actuelle:Comment éviter la dupilcation de tests si j'ai une classe abstraite

class AbstractEngine() 
{ 
    public: 
     virtual int calculateGasolineConsumption() 
     { 
      //... do calculation ... 
      return consumption; 
     } 

     // some other (pure virtual) methodes 
}; 

class EngineA() : public AbstractEngine 
{ 
    public:        
     // implementation of the pure virtual methodes 
}; 

class EngineB() : public AbstractEngine 
{ 
    public:        
     // implementation of the pure virtual methodes 
}; 

class EngineC() : public AbstractEngine 
{ 
    public:        
     // implementation of the pure virtual methodes 
     int calculateGasolineConsumption() override 
     { 
      //... do new calculation ... 
      return consumption;         
     } 
}; 
enum EngineType { 
    ENGINE_A, 
    ENGINE_B, 
    ENGINE_C, 
}; 

void configEngine(EngineType engineType) 
{ 
    m_engine = m_engineFactory.create(engineType); 
} 

int currentGasolineConsumption() 
{ 
    return m_engine.calculateGasolineConsumption(); 
} 

Maintenant, ma question est de savoir comment UnitTest cela sans faire double emploi dans mes tests unitaires? Si j'écris trois unittests, pour configEngine (ENGINE_A) et configEngine (ENGINE_B) testerait fondamentalement le même code de la superclasse abstraite et je n'aime pas cette duplication.

struct EngineSpec { 
    EngineType engineType; 
    int expectedValue; 
}; 

INSTANTIATE_TEST_CASE_P(, tst_car, ::testing::Values(
    EngineSpec { ENGINE_A, 3 }, 
    EngineSpec { ENGINE_B, 3 }, 
    EngineSpec { ENGINE_C, 7 } 
)); 

TEST_F(tst_car, 
     currentGasolineConsumption_configWithEngine_expectedBehaviour) 
{ 
    EngineSpec engineSpec = GetParam(); 

    //Arrange 
    m_car.configEngine(engineSpec.engineType); 

    //Act 
    auto result = m_car.currentGasolineConsumption(); 

    //Assert 
    EXPECT_EQ(engineSpec.expectedValue, result); 
} 

Bien sûr, il n'y a qu'un seul duplication/unittest inutile, mais ce n'est qu'un exemple minimal. Dans mon code réel, le nombre de duplications de tests unitaires exploserait. Une autre chose: je ne veux pas déplacer la classe Engine en dehors du 'module' et utiliser l'injection de dépendance parce que je pense que cette approche de 'classe interne du moteur' est plus facile à gérer pour le client. Le client n'a donc qu'une seule interface et quelques enums pour utiliser ce module. Je voudrais traiter la classe Engine comme détail d'implémentation.

+0

Les tests pour 'configEngine' sont super simples. Vous vous moquez de 'm_engineFactory', dites-lui de retourner un moteur et c'est la fin de l'histoire. Chaque test de sous-classe de moteur causera probablement un problème de duplication - est-ce ce que vous demandez ici? –

+0

Je viens d'ajouter le code du test paramétré (google test). Il s'agit plutôt de tester currentGasolineConsumption. configEngine est juste l'arrangement ... – avb

Répondre

0

Idéalement, tests devraient en savoir aussi peu sur l'implémentation que possible, car 10 ans plus tard lorsque l'abstraction ne fonctionne plus tout à fait, ou fait partie d'une grande chaîne d'héritage compliquée (par exemple ce qui se passe lorsque vous obtenir un moteur hybride?) les tests qui semblent être beaucoup d'efforts en ce moment fonctionneront toujours parfaitement.

Cependant, si vous voulez être pragmatique et que vous ne vous souciez pas de coupler un peu vos tests à l'implémentation, vous pouvez extraire une méthode testGasolineConsumption(AbstractEngine engine) appelée à partir d'un scénario de test pour chaque enfant. Cela vérifiera que l'implémentation fonctionne correctement et que le comportement de la classe de base n'a pas été remplacé.