Question

À des fins de tests unitaires, je dois simuler une réponse réseau.La réponse est normalement un flux d'octets, stocké sous forme de const vector<uint8_t>.Cependant, pour le test unitaire, je souhaite produire le vecteur avec des données codées en dur dans le fichier CPP ou lues à partir d'un fichier dans la même solution.Mes données d'exemple font environ 6 Ko.Quelles sont les directives générales sur l'endroit où placer les données lors de l'utilisation test google?

Était-ce utile?

La solution

Peut-être (a) vous avez besoin d'une grande séquence de données pour un rôle dans lequel les cas de test vont simplement les lire.Il peut tout aussi bien s'agir de données globales (de classe), avecconst accéder.

Peut-être (b) vous avez besoin d'une grande séquence de données pour un rôle dans lequel les cas de test liront et les modifieront ou les détruiront.Cela doit être réinitialisé par cas de test et avoir nonconst accéder.

Peut-être les deux.Dans les deux cas, une implémentation Googlest conventionnelle utiliserait un appareil d'essai pour encapsuler l’acquisition des données, les acquerrait dans le Mise en œuvre de la mise en œuvre virtuelle de l’appareil Setup() la fonction membre, et Accédez-y par le biais d’une méthode getter de l’appareil.

Le programme suivant illustre un appareil qui fournit à la fois par cas données mutables et données constantes globales acquises à partir de fichiers.

#include <vector>
#include <fstream>
#include <stdexcept>
#include "gtest/gtest.h"

class foo_test : public ::testing::Test
{
protected:
    virtual void SetUp() {
        std::ifstream in("path/to/case_data");
        if (!in) {
            throw std::runtime_error("Could not open \"path/to/case_data\" for input");
        }
        _case_data.assign(
            std::istream_iterator<char>(in),std::istream_iterator<char>());
        if (_global_data.empty()) {
            std::ifstream in("path/to/global_data");
            if (!in) {
                throw std::runtime_error(
                    "Could not open \"path/to/global_data\" for input");
            }
            _global_data.assign(
                std::istream_iterator<char>(in),std::istream_iterator<char>());
        }
    }
    // virtual void TearDown() {}   
    std::vector<char> & case_data() {
        return _case_data;
    }
    static std::vector<char> const & global_data() {
        return _global_data;
    }

private:
    std::vector<char> _case_data;
    static std::vector<char> _global_data;

};

std::vector<char> foo_test::_global_data;

TEST_F(foo_test, CaseDataWipe) {
  EXPECT_GT(case_data().size(),0);
  case_data().resize(0);
  EXPECT_EQ(case_data().size(),0);
}

TEST_F(foo_test, CaseDataTrunc) {
  EXPECT_GT(case_data().size(),0);
  case_data().resize(1);
  EXPECT_EQ(case_data().size(),1);
}

TEST_F(foo_test, HaveGlobalData) {
  EXPECT_GT(global_data().size(),0);
}


int main(int argc, char **argv) {
  ::testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}

Pour le cas (a), vous pouvez également envisager d'acquérir les données dans un Configuration globalefonction membre par sous-classement ::testing::Environment, mais je ne vois pas raison générale de préférer le faire de cette façon.

...Ou le coder en dur ?

Ensuite, la question de savoir s’il faut conserver les données de test dans un fichier ou les coder en dur dans la source de test. Les lecteurs qui sont satisfaits à ce stade ne feront que s'ennuyer désormais.

D’une manière générale, il s’agit d’une question de jugement dans les circonstances, et je ne pense pas que que l’utilisation de GoogleTest fait pencher la balance de manière significative.Je pense que la principale considération est: Est-il souhaitable de pouvoir varier un élément de données de test sans reconstruire la suite de test?

Supposons que la reconstruction de la suite de tests pour faire varier cet élément soit un coût non négligeable et que vous prévoir que le contenu de l’article variera à l’avenir de manière indépendante du code de test associé.Ou il peut varier, indépendamment du code de test associé, pour différentes configurations du système testé.Dans ce cas, il est préférable d’obtenir le d’un fichier ou d’une autre source qui peut être sélectionné par les paramètres d’exécution de la suite de tests.Dans googletest, sous-classement class ::testing::Environment est un Installation conçue pour l’acquisition paramétrée des ressources de la suite de tests.

Si en réalité Le contenu d’un élément de données de test est faiblement couplé à le code de test associé, puis le coder en dur dans des cas de test est très improbable d’être un choix prudent.(Et les fichiers de test, par opposition à d’autres types d’exécution configurateurs, ont la précieuse propriété qu’ils peuvent être contrôlés en version dans le même système que le code source.)

Si le contenu d'une donnée de test est solidement couplé à la code de test associé, je suis biaisé pour le coder en dur plutôt que de l’extraire à partir d’un fichier de données.Juste partial, pas dogmatiquement engagé.Peut-être que votre test utilise des fonctionnalités de bibliothèque robustes pour initialiser les données de test d’API publiques à partir de, par exemple, des fichiers XML qui sont également connectés à la gestion des tests et à la gestion des défauts, Systèmes.Bien!

Je considère qu’il est tout à fait souhaitable que si un fichier de données d’essai est un ressource de test - une ressource que la suite de tests ne peut pas générer - puis son contenu Il est préférable d’avoir des données textuelles qu’un mainteneur compétent peut facilement comprendre et manipuler.Dans ce contexte, je considérerais bien sûr qu’un liste des constantes hexadécimales C/C++, par exemple, est données textuelles -C’est vrai code source.Si un fichier de test contient des données binaires ou orientées machine alors la suite de tests devait mieux contenir les moyens de sa production à partir de lisibles ressources primaires.Il est parfois inévitable qu’une suite de tests dépende de binaires « archétypaux » d’origine externe, mais ils impliquent presque inévitablement la Sinistre spectacle d’ingénieurs de test et de correcteurs de bogues qui grisonnent devant les éditeurs hexadécimaux.

Étant donné le principe selon lequel les données de test primaires doivent être lisibles pour les mainteneurs, nous peut considérer comme une norme que les données de test primaires seront « une sorte de code » :Il sera sans logique, mais ce sera le genre de trucs textuels que les programmeurs sont habitués à l’arpentage et à l’édition.

Imaginons qu’une séquence particulière de 4096 entiers non signés 64 bits (la grande table magique) est nécessaire pour tester votre logiciel et est étroitement attaché au code de test associé.Il peut être codé en dur sous la forme d’un énorme vecteur ou d’un tableau initialiseur dans un fichier source de la suite de tests.Il pourrait s’agir d’une extraites par la suite de tests à partir d’un fichier de données maintenu au format CSV ou en Lignes ponctuées au format CSV.

Pour l’extraction à partir d’un fichier de données et contre le codage en dur, il peut être recommandé (selon la réponse d’Andrew McDonell) que cela permet de démêler révisions de la BMT à partir de révisions d’autres codes dans le même fichier source.De même, il pourrait être demandé que tout code source qui encadre Les initialisations littérales énormes ont tendance à ne pas être relevables et donc à responsabilité.

Mais ces deux points peuvent être contrés par l’observation que la définition La déclaration de la BMT peut être codée dans un fichier source qui lui est propre.Il Il peut s’agir d’une stratégie de révision de code pour la suite de tests qui initialise les données de test doit codé - et peut-être dans des fichiers qui adhèrent à une dénomination distinctive convention.Une politique fanatique, certes, mais pas plus fanatique que qui insisterait sur le fait que tous les initialiseurs de données de test doivent être extraits des fichiers.Si un mainteneur est obligé d’examiner la BMT dans n’importe quel fichier qui la contient, Il n’y aura aucune différence si l’extension du fichier est .cpp, .dat ou quoi que:tout ce qui compte est l'intelligibilité du "code".

Pour le codage en dur et contre l’extraction à partir d’un fichier de données, il peut être recommandé que l’extraction à partir d’un fichier de données doit introduire une source de défaillances potentielles dans les cas de test - tous les Cela ne devrait pas arriver erreurs qui pourrait aller à l’encontre de la lecture des bonnes données à partir d’un fichier.Cela impose une surcharge à l’élaboration d’un test afin d’effectuer une distinction correcte et claire entre les échecs de test réels et les l’incapacité d’acquérir les données d’essai à partir du dossier et de diagnostiquer clairement tous les causes de ce dernier.

Dans le cas de googletest et de frameworks fonctionnels comparables, ce point peut être contrée, dans une certaine mesure, par l’introduction de classes de base de bridage polymorphes comme ::testing::Test et ::testing::Environment.Ceux-ci facilitent le développeur de test dans l’encapsulation de l’acquisition de ressources de test dans le cas de test ou l’initialisation de la suite de tests pour que tout soit terminé, avec succès ou avec avec une défaillance diagnostiquée, avant que les tests constitutifs d’un cas de test ne soient exécutés.RAII peut maintenir une division sans problème entre les échecs de configuration et les échecs réels.

Néanmoins, il existe une surcharge irréductible de traitement des fichiers pour le fichier de données route et il y a une surcharge opérationnelle que le RAII présente dans le cadre ne rien faire pour réduire.Dans mes rapports avec des systèmes de test lourds qui se négocient sur fichiers de données, les fichiers de données je suis juste plus sujettes aux incidents opérationnels que fichiers sources qui ne doivent être présents et corrects qu’au moment de la construction.Les fichiers de données sont plus susceptibles d’être manquants ou égarés pendant l’exécution, ou contenant des trucs difformes, ou d’une manière ou d’une autre d’avoir été refusés, ou d’une manière ou d’une autre d’apparaître à la mauvaise révision.Leurs utilisations dans un système de test ne sont pas aussi simples ou aussi rigidement contrôlés que ceux des fichiers sources. Des choses qui ne devraient pas arriver fichiers de données de test fait partie de la friction opérationnelle de systèmes de test qui en dépendent et est proportionnel à leur nombre.

Depuis fichiers source Peut encapsuler les initialisations des données de test de manière hygiénique Pour le suivi des révisions, leur codage en dur peut être assimilé à l’extraction d’un , le préprocesseur effectuant l’extraction en tant que sous-produit de la compilation.Dans cette optique, pourquoi employer d’autres machines, avec des responsabilités supplémentaires, pour l’extraire ?Il peut y avoir de bonnes réponses, comme l’interface XML suggérée avec la gestion des tests, systèmes de gestion des défauts, mais « Ce sont des données de test, alors ne les codez pas en dur » n’est pas une bonne idée.

Même si une suite de tests doit prendre en charge diverses configurations du système sous test qui font appel à diverses instanciations d’un élément de données de test, si les données item est convariant à construire les configurations de la suite de tests, vous pouvez en tant que Eh bien, toujours (hygiéniquement) codez-le en dur et laissez la compilation conditionnelle sélectionner le bon codage en dur.

Jusqu’à présent, je n’ai pas contesté l’argument de l’hygiène de suivi des révisions pour la ségrégation basée sur les fichiers des initialiseurs de données de test.Je viens de faire le que les fichiers sources normaux dans lesquels les initialiseurs sont codés en dur peuvent d’accomplir cette ségrégation.Et je ne veux pas démentir cet argument, mais Je veux m’arrêter avant la conclusion fanatique que les initialiseurs de données de test devraient en principe toujours être extraites de fichiers dédiés - qu’il s’agisse de fichiers sources ou de fichiers de données.

Il n’est pas nécessaire d’insister sur les raisons pour lesquelles on s’oppose à cette conclusion.Par là lie le code de test qui est localement moins intelligible que la moyenne des mangeurs de pizza programmeur écrirait et organiserait des fichiers de suite de tests qui se développent ahurissante beaucoup plus rapidement qu’il n’est nécessaire ou sain.D’un point de vue normatif, Toutes les ressources primaires de la suite de tests sont « une sorte de code ».Un Les compétences du programmeur comprennent la capacité de partitionner le code en fichiers avec une granularité appropriée pour garantir l’hygiène de suivi des révisions appropriée.Ce n'est pas une procédure mécanique, c'est une expertise, à couvrir par la révision du code.Cependant, la revue de code peut et doit garantir que les initialisations des données de test Ils sont accomplis, sont bien conçus et conçus vis-à-vis du suivi des révisions comme dans tous les autres aspects de la routine.

Conclusion :Si vous voulez être en mesure d’exécuter la même version de votre suite de tests pour une variété de de ces réponses réseau fictives, lisez-les à partir d’un fichier.Si, par contre, il est invariant ou covariant avec les configurations de build de la suite de tests, pourquoi pas dur le coder ?

Autres conseils

(Attention : cette réponse est générique pour tout framework de tests unitaires)

Je préfère conserver les fichiers de données de test en tant qu'objets distincts dans un système de contrôle de révision.Cela offre les avantages suivants :

  • vous pouvez coder le test unitaire pour accepter un ou plusieurs fichiers de données afin de tester diverses situations
  • vous pouvez suivre les modifications des données selon vos besoins

Si vous ne souhaitez pas que l'exécution du test unitaire lise un fichier de données, ce qui peut être une condition nécessaire dans certaines situations, vous pouvez choisir d'écrire un programme ou un script qui génère du code C++ qui initialise le vecteur lors de la configuration de l'appareil.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top