[PYTHON] Comment faire un test unitaire Part.2 Conception de classe pour les tests

introduction

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 l'article précédent (http://qiita.com/progrommer/items/a04a74d8b9f43eaef4b9), j'ai présenté le modèle Proxy et le modèle Monostate, qui ont des concepts de base et des formes faciles à utiliser. Cette fois, je décrirai comment concevoir la classe à tester unitaire. Dans la continuité de la dernière fois, le thème est l'injection de dépendances (DI).

L'exemple de programme est un mélange de Python, C # et C ++, mais nous prévoyons de le compléter en temps voulu. S'il vous plaît, pardonnez-moi.

Séparation de la génération et de l'utilisation

Même endroit pour la génération et l'utilisation

Certaines implémentations de classe (ou de fonction) difficiles à tester sont créées et utilisées au même endroit.

// Python
class Some:
    def get(self):
        return 42

def func():
    some = Some()
    value = some.get()
    if value < 10:
        pass
// C++
struct Some {
    int get() const {return 42;}
};

void func() {
    if (Some().get() < 10) return;
}

Dans cet exemple, une instance de la classe Some est créée à la volée et la fonction est appelée. Cela rend impossible toute intervention dans Some, même si vous essayez de tester la fonction func.

Un autre exemple

Soyez prudent lorsque vous créez un membre dans le constructeur, puis que vous l'utilisez dans n'importe quelle fonction. Dans l'exemple suivant, le code de test de la classe Target peut remplacer m_some après avoir généré Some, et il y a de la place pour une intervention. À première vue, cela semble bon. Cependant, si vous appelez Some à partir d'une autre classe ou fonction, vous ne pouvez pas tester cette classe ou cette fonction.

// C++
Target::Target()
  : m_some(new Some("abc"))
{
}

Target::exec() {
    m_some->get();
}

//Une autre fonction à tester
void func() {
    Target target;
    target.exec();  //Original Some est généré et utilisé dans Target
}

Utiliser des modèles pour la génération

Si la génération et l'utilisation fixes mentionnées ci-dessus sont effectuées, le test unitaire devient difficile. Une fois qu'une seule de ces classes est créée, il est difficile de la démêler. Par conséquent, il est souhaitable de concevoir la classe en séparant la génération et l'utilisation du stade le plus précoce. Il existe plusieurs façons de procéder, mais voici quelques exemples qui sont en fait faciles à utiliser.

Exemple d'usine abstraite

Il existe plusieurs modèles de "génération" dans le modèle de conception, mais utilisons l'un d'entre eux, le modèle de fabrique abstraite.

AbstractFactory.png

Dans l'exemple suivant, la classe utilisateur Client utilise un générateur appelé Creator. Vous créez les instances Logger et File dont vous avez besoin à partir de l'instance Creator que vous avez reçue. La substance de Creator est TestCreator créé dans main (), et il a été remplacé par une classe de test qui sort sur la console et génère un fichier factice.

Nom de classe dans la figure Exemple de nom de classe
ICreator Creator
TargetCreator Aucun
Target1 Logger
Target2 File
Target1Object Aucun
Target2Object Aucun
ClientTest fonction principale
DummyCreator TestCreator
Dummy1Object ConsoleLogger
Dummy2Object DummyFile
# Python
class Logger:
    def write(self, msg: str) -> None:
        pass

class ConsoleLogger(Logger):
    def write(self, msg: str) -> None:
        print(msg)

class File:
    pass

class DummyFile(File):
    pass

class Creator:
    def create_logger(self) -> Logger:
        return None
    def create_file(self) -> File:
        return None

class TestCreator(Creator):
    def create_logger(self) -> Logger:
        return ConsoleLogger()
    def create_file(self) -> File:
        return DummyFile()

class Client:
    def __init__(self, creator: Creator):
        self._logger = creator.create_logger()
        self._file = creator.create_file()
    def exec(self) -> None:
        #Quelque chose de traitement
        self._logger.write('Réussite')

def main():
    creator = TestCreator()
    client = Client(creator)

    client.exec()

Exemple de singleton

Un autre exemple de modèle de «génération» est Singleton. Singleton ne peut pas intervenir car la génération est fermée à l'intérieur de sa classe. Le but est de ne pas pouvoir intervenir, mais il est difficile à utiliser pour les tests.

Ici, nous assouplissons un peu la structure de Singleton et préparons une bouche pour l'intervention. L'instance est protégée et toutes les méthodes utilisent NVI ou simplement virtuelles.

Singleton1.png

Voici un exemple de remplacement de la classe Singleton et de la classe de test en C #. Le deuxième appel à Print () qui bascule vers Dummy imprime "Dummy" pour la même méthode. Au moment du test unitaire, il doit être défini dans le processus préparatoire afin que Dummy soit appelé dès le début.

// C#
public class Some {
    protected static Some instance;
    static Some() {
        instance = new Some();
    }
    static public Some getInstance() {
        return instance;
    }
    virtual public void Print() {
        Console.WriteLine("Some");
    }
}

internal class SomeDummy : Some {
    internal static void UseDummy() {
        instance = new SomeDummy();
    }
    public override void Print() {
        Console.WriteLine("Dummy");
    }
}

public class Client {
    static void Main(string[] args) {
        {
            var target = Some.getInstance();
            target.Print();    // "Some"Et sortie
        }

        SomeDummy.UseDummy();  //Remplacer Singleton
        {
            var target = Some.getInstance();
            target.Print();    // "Dummy"Et sortie
        }
    }
}

application

L'usine abstraite peut être ennuyeuse car elle doit passer l'usine à la génération de classe. Des applications telles que la fourniture d'une usine comme Singleton ou Monostate peuvent être envisagées. Bien sûr, au moment du test unitaire, renvoyez une usine qui génère une classe factice pour le test. En utilisant ceci pour remplacer le premier exemple:

// Python
class FactoryServer:
    some_creator = SomeCreator()

def func():
    some = FactoryServer.some_creator()
    value = some.get()
    if value < 10:
        pass

Résumé

Un exemple de séparation de la génération et de l'utilisation est présenté.

Dans l'exemple de l'usine abstraite, nous avons montré une structure dans laquelle la classe réelle et la classe pour les tests unitaires peuvent être librement remplacées en faisant abstraction de l'objet pour la création et l'utilisation. Dans l'exemple Singleton, le produit est intervenu pour utiliser une instance de test. En tant qu'application, en combinant Singleton / Monostate et Abstract factory, nous avons donné une configuration dans laquelle l'intervention de test peut être établie en même temps tout en conservant la simplicité de mise en œuvre.

Autre

Recommended Posts

Comment faire un test unitaire Part.2 Conception de classe pour les tests
Comment faire un test unitaire Part.1 Modèle de conception pour l'introduction
[Python] Comment rendre une classe itérable
[Cocos2d-x] Comment créer une liaison de script (partie 2)
[Cocos2d-x] Comment créer une liaison de script (partie 1)
Comment créer un plug-in Spigot (pour les débutants Java)
Comment rendre le Python des débutants plus rapide [numpy]
Comment faire un jeu de tir avec toio (partie 1)
Comment créer un laboratoire de piratage - Kali Linux (2020.1) VirtualBox 64 bits Partie 2-
Comment créer un package Python (écrit pour un stagiaire)
[Pour les non-programmeurs] Comment marcher Kaggle
Comment créer un bot slack
Comment créer un robot - Avancé
Comment créer une fonction récursive
Spigot (Paper) Introduction à la création d'un plug-in pour 2020 # 01 (Construction de l'environnement)
[Blender] Comment créer un plug-in Blender
[Blender] Comment rendre les scripts Blender multilingues
Comment créer un robot - Basic
Comment faire un modèle pour la détection d'objets avec YOLO en 3 heures
Comment rendre les caractères de Word Cloud monochromatiques
Comment rendre le sélénium aussi léger que possible
[Introduction à Python] Comment utiliser la classe en Python?
Comment utiliser le réseau de développeurs cybozu.com (partie 2)
Décorateur pour test unitaire utilisant des nombres aléatoires
Comment créer un fichier * .spec pour pyinstaller.
Comment utiliser Tweepy ~ Partie 1 ~ [Obtenir un Tweet]
[Python] Organisation de l'utilisation des instructions
J'ai écrit un test unitaire pour différentes langues
Comment installer le sous-système Windows pour Linux
Comment utiliser __slots__ dans la classe Python
Comment créer une clé USB à démarrage multiple (compatible Windows 10)
Comment utiliser "deque" pour les données Python
Comment créer un indicateur personnalisé Backtrader
Comment créer un plan de site Pelican
Comment utiliser l'authentification par empreinte digitale pour KDE
[Pour l'enregistrement] Système d'image Keras Partie 1: Comment créer votre propre ensemble de données?
Comment créer un système de dialogue dédié aux débutants
Comment créer un pilote de périphérique Linux intégré (11)
Comment utiliser MkDocs pour la première fois
Comment faire correspondre WTForms TextArea à la suppression de fichier
Comment créer un pilote de périphérique Linux intégré (8)
Comment créer un pilote de périphérique Linux intégré (1)
Comment créer un pilote de périphérique Linux intégré (4)
Comment rendre plusieurs noyaux sélectionnables sur Jupyter
Comment utiliser Template Engine pour Network Engineer
Comment créer un dictionnaire avec une structure hiérarchique.
Comment créer un pilote de périphérique Linux intégré (7)
Comment créer un pilote de périphérique Linux intégré (2)
Comment créer une sortie JSON Scintillante en japonais
Comment créer un pilote de périphérique Linux intégré (3)
Comment installer Python pour les chercheurs de sociétés pharmaceutiques
Comment utiliser les outils d'analyse de données pour les débutants
Comment utiliser Tweepy ~ Partie 2 ~ [Suivez, aimez, etc.]