→ Cliquez ici pour la partie 2
Afin d'exécuter le test unitaire, il est nécessaire de remplacer / d'éliminer les éléments dépendant de l'environnement tels que les périphériques. Si ces manipulations ne sont pas soigneusement conçues, les tests unitaires ne seront pas possibles. Dans une série d'articles de ce titre, nous examinerons les implémentations pour les tests et la production, en utilisant des modèles de conception. Dans cet article, nous examinerons comment gérer les données conservées en mémoire, mais il en va de même pour les appareils tels que les réseaux.
En fin de compte, l'accent est mis sur la façon de remplacer le module de test. Cette idée est appelée injection de dépendance (DI). En DI, le degré de couplage entre les modules est réduit via l'interface.
L'exemple de programme est écrit en C ++, mais C # et Python seront également écrits.
2015-04-26 Ajout de l'exemple de code Python
Les données peuvent être tout ce qui détermine le comportement de l'application ou transmet le résultat du comportement. Vous pouvez le faire localement, mais il y a des moments où vous voulez vraiment l'utiliser globalement. Comment doit-il être conservé?
L'exemple suivant suppose une fonction qui change son comportement en fonction de la version du module externe.
//Avant l'introduction (non testable ou difficile)
void func() {
//Obtenir la version
int version = SomeModule::getMajorVersion();
if (version > 10) {
//Quelque chose à voir avec la version spécifiée
}
}
Ici, on suppose que SomeModule :: getMajorVersion ()
ne peut être utilisé que dans un environnement spécifique, ou il est difficile de changer d'état. Avec un tel code, il est difficile de tester l'instruction if suivante.
Ici, créez une classe factice qui remplace SomeModule
, préparez une entité et un proxy qui accède au mannequin et rendez-le testable.
Tout d'abord, à partir de la classe Proxy Interface, qui est le point de contact.
class ModuleProxy {
virtual int getMajorVersion_() const = 0;
protected:
ModuleProxy() {}
public:
virtual ~ModuleProxy() {};
int getMajorVersion() const {
return this->getMajorVersion_();
}
};
Vient ensuite la classe réelle et la classe factice. Ici, le SomeModule d'origine est également masqué et la classe d'utilisation est créée.
//Classe qui appelle réellement le module externe
class SomeModuleImpl : public ModuleProxy {
virtual int getMajorVersion_() const override {
return SomeModule::getMajorVersion();
}
};
//Classe factice pour les tests
class ModuleDummy : public ModuleProxy {
virtual int getMajorVersion_() const override {
return major_;
}
public:
ModuleDummy(int ver = 0)
: major_(ver)
{}
int major_;
};
La fonction func et le code de test sont les suivants.
void func(ModuleProxy& module) {
//Obtenir la version
int version = module.getMajorVersion();
if (version > 10) {
}
}
void XXXTest::funcTest() {
ModuleDummy dummy(10);
{
func(dummy);
}
{
dummy.major_ = 11;
func(dummy);
}
}
Une autre façon de l'implémenter consiste à utiliser le modèle Monostate. Ici, la fonction membre est également statique, mais cela peut être une fonction normale.
class Data {
static int majorVersion;
public:
static int getMajorVersion() const {
return majorVersion;
}
friend class XXXTest;
};
int Data::majorVersion = 1;
L'implémentation d'application qui l'utilise est la suivante.
void func() {
if (Data::getMajorVersion() > 10) {
}
}
Le côté test ressemble à ceci.
void XXXTest::funcTest() {
{
Data::majorVersion = 10;
func();
}
{
Data::majorVersion = 11;
func();
}
}
Le modèle Monostate est simple, mais vous devez penser à l'initialisation de la valeur. Lorsqu'il est référencé par d'autres éléments statiques, il faut prendre soin de l'ordre d'initialisation.
La version Python du modèle Monostate est illustrée ci-dessous. La méthode de classe a été spécifiée pour la méthode.
class Data:
_majorVersion = 1
@classmethod
def getMajorVersion(cls) -> int:
return cls._majorVersion
Le côté test ressemble à ceci:
import unittest
class Test_testSample(unittest.TestCase):
def test_version(self):
Monostate.Data._majorVersion = 10
self.assertFalse(Target.func())
Monostate.Data._majorVersion = 11
self.assertTrue(Target.func())
Dans cet article, DI a été réalisé en utilisant le modèle Proxy. Nous avons également montré comment utiliser plus simplement le modèle Monostate.
Il est important d'être conscient des contrats, même dans des langages tels que C ++ / C # / Python qui ne prennent pas en charge la fonctionnalité de contrat.
Puisque Monostate peut être utilisé de manière polymorphe, il est possible d'incorporer des modèles Proxy pour le rendre plus facile à utiliser.
Il est bon d'utiliser friend dans le test unitaire, mais cela ajoutera un ami à l'en-tête côté application, ce qui provoquera une recompilation. Réfléchissez à la façon d'y faire face.
Lors de l'implémentation de l'accès aux ressources avec shared_ptr, il peut être préférable d'utiliser low_ptr comme IF de Proxy. Essayez ces améliorations tout en restant testables.
Recommended Posts