80 votes

Quelle est la meilleure façon de tester des méthodes privées avec GoogleTest?

Je voudrais tester certaines méthodes privées à l'aide de GoogleTest.

class Foo
{
private:
    int bar(...)
}

GoogleTest permet à un couple de façons de le faire.

OPTION 1

Avec FRIEND_TEST:

class Foo
{
private:
    FRIEND_TEST(Foo, barReturnsZero);
    int bar(...);
}

TEST(Foo, barReturnsZero)
{
    Foo foo;
    EXPECT_EQ(foo.bar(...), 0);
}

Cela implique d'inclure "gtest/gtest.h" dans la production du fichier source.

OPTION 2

Déclarer un montage de test, comme un ami de la classe et de définir des accesseurs dans l'appareil:

class Foo
{
    friend class FooTest;
private:
    int bar(...);
}

class FooTest : public ::testing::Test
{
protected:
    int bar(...) { foo.bar(...); }
private:
    Foo foo;
}

TEST_F(FooTest, barReturnsZero)
{
    EXPECT_EQ(bar(...), 0);
}

OPTION 3

Le Pimpl idiome.

Pour plus de détails: Google Test: guide Avancé.

Existe-il d'autres moyens de tester les méthodes privées? Quels sont les avantages et les inconvénients de chaque option?

90voto

Matt Messersmith Points 4399

Il y a au moins deux autres options. Je vais énumérer quelques autres options que vous devriez considérer en expliquant une situation donnée.

Option 4:

Envisager la refactorisation de votre code, de sorte que la partie que vous voulez de test public est dans une autre classe. Généralement, quand vous êtes tenté de tester une classe privée de méthode, c'est un signe de mauvaise conception. L'une des plus courantes (anti)paterns que je vois, c'est ce que Michael Plumes appelle un "Iceberg" de la classe. "Iceberg" les classes disposent d'une méthode publique, et le reste en privé (c'est pourquoi il est tentant de tester les méthodes privées). Il pourrait ressembler à quelque chose comme ceci:

RuleEvaluator (stolen from Michael Feathers)

Par exemple, vous pouvez tester GetNextToken() en l'appelant sur une chaîne successivement et de voir qu'il renvoie le résultat attendu. Une fonction comme ceci ne garantit un test: ce comportement n'est pas négligeable, surtout si vos jetons règles sont complexes. Faisons semblant de croire que ce n'est pas tout le complexe, et nous voulons juste de la corde en jetons délimité par l'espace. Si vous écrivez un essai, peut-être que ça ressemble à quelque chose comme ceci:

TEST(RuleEvaluator, canParseSpaceDelimtedTokens)
{
    std::string input_string = "1 2 test bar";
    RuleEvaluator re = RuleEvaluator(input_string);
    EXPECT_EQ(re.GetNextToken(), "1");
    EXPECT_EQ(re.GetNextToken(), "2");
    EXPECT_EQ(re.GetNextToken(), "test");
    EXPECT_EQ(re.GetNextToken(), "bar");
    EXPECT_EQ(re.HasMoreTokens(), false);
}

Eh bien, que fait l'air assez sympa. Nous voulons nous assurer de maintenir ce comportement que nous devons apporter des modifications. Mais GetNextToken() est un privé fonction! On ne peut donc pas tester de cette façon, car il l'habitude de même de la compilation. Mais ce que sur la modification de l' RuleEvaluator classe de suivre le Principe de Responsabilité Unique (Principe de Responsabilité Unique)? Par exemple, nous semblent avoir un analyseur générateur de jetons, et l'évaluateur coincé dans une classe. Ne serait-il pas mieux de séparer les responsabilités? En plus de cela, si vous créez un Tokenizer classe, puis c'est au public les méthodes seraient HasMoreTokens() et GetNextTokens(). L' RuleEvaluator d'une classe pourrait avoir un Tokenizer objet en tant que membre. Maintenant, nous pouvons faire le même test que précédemment, sauf que nous testons l' Tokenizer classe à la place de l' RuleEvaluator classe.

Voici à quoi il pourrait ressembler dans UML:

Refactored RuleEvaluator class

Notez que cette nouvelle conception augmente la modularité, de sorte que vous pourriez potentiellement ré-utiliser ces classes dans d'autres parties de votre système (avant on ne pouvait pas, des méthodes privées ne sont pas réutilisables par définition). C'est le principal avantage de briser le RuleEvaluator vers le bas, le long de avec l'augmentation de l'intelligibilité/localité.

Le test de look très similaire, sauf qu'il serait en fait la compilation de cette fois depuis l' GetNextToken() méthode est maintenant public sur l' Tokenizer classe:

TEST(Tokenizer, canParseSpaceDelimtedTokens)
{
    std::string input_string = "1 2 test bar";
    Tokenizer tokenizer = Tokenizer(input_string);
    EXPECT_EQ(tokenizer.GetNextToken(), "1");
    EXPECT_EQ(tokenizer.GetNextToken(), "2");
    EXPECT_EQ(tokenizer.GetNextToken(), "test");
    EXPECT_EQ(tokenizer.GetNextToken(), "bar");
    EXPECT_EQ(tokenizer.HasMoreTokens(), false);
}

Option 5

Il suffit de ne pas tester les fonctions privées. Parfois, ils ne valent pas les tests, car ils seront testés par le biais de l'interface publique. Un grand nombre de fois ce que je vois, c'est des tests qui semblent très similaires, mais de tester les deux fonctions différentes méthodes. Ce qui finit par arriver, c'est que lorsque les besoins changent (et ils le font toujours), vous avez maintenant 2 tests cassés au lieu de 1. Et si vous avez vraiment testé tout vos méthodes, vous pouvez avoir plus de 10 tests cassés au lieu de 1. En bref, les tests des fonctions privées (à l'aide d' FRIEND_TEST ou de les rendre publiques) qui, autrement, pourraient être testés au moyen d'une interface publique provoquer test de la duplication. Vous ne voulez vraiment pas à cela, parce que rien ne blesse plus que votre suite de tests vous ralentir. Il est censé diminuer les temps de développement et de diminution des coûts de maintenance! Si vous testez les méthodes privées qui autrement sont testés par le biais d'une interface publique, de la suite de test peut très bien faire l'inverse, et activement à l'augmentation des coûts de maintenance et augmenter le temps de développement. Lorsque vous effectuez un privé, de la fonction publique, ou si vous utilisez quelque chose comme FRIEND_TEST, vous aurez généralement finissent par regretter.

Considérez les points suivants possibles de mise en œuvre de l' Tokenizer classe:

Possible impl of Tokenizer

Disons qu' SplitUpByDelimiter() est chargée de retourner un std::vector<std::string> , de sorte que chaque élément du vecteur est un jeton. En outre, disons simplement que l' GetNextToken() est tout simplement un itérateur sur ce vecteur. Si vos tests pourrait ressembler à ceci:

TEST(Tokenizer, canParseSpaceDelimtedTokens)
{
    std::string input_string = "1 2 test bar";
    Tokenizer tokenizer = Tokenizer(input_string);
    EXPECT_EQ(tokenizer.GetNextToken(), "1");
    EXPECT_EQ(tokenizer.GetNextToken(), "2");
    EXPECT_EQ(tokenizer.GetNextToken(), "test");
    EXPECT_EQ(tokenizer.GetNextToken(), "bar");
    EXPECT_EQ(tokenizer.HasMoreTokens(), false);
}

// Pretend we have some class for a FRIEND_TEST
TEST_F(TokenizerTest, canGenerateSpaceDelimtedTokens)
{
    std::string input_string = "1 2 test bar";
    Tokenizer tokenizer = Tokenizer(input_string);
    std::vector<std::string> result = tokenizer.SplitUpByDelimiter(" ");
    EXPECT_EQ(result.size(), 4);
    EXPECT_EQ(result[0], "1");
    EXPECT_EQ(result[1], "2");
    EXPECT_EQ(result[2], "test");
    EXPECT_EQ(result[3], "bar");
}

Bien, maintenant, disons les exigences de changement, et maintenant, vous êtes attendus à analyser un par un "," au lieu d'un espace. Naturellement, vous allez attendre un test à la pause, mais la douleur augmente lorsque vous testez les fonctions privées. OMI, google test ne doit pas permettre FRIEND_TEST. Il n'est presque jamais ce que vous voulez faire. Michael Plumes se réfère à des choses comme l' FRIEND_TEST comme un "outil groping", puisque c'est en essayant de toucher les parties intimes d'autrui.

Je recommande d'éviter l'option 1 et 2 lorsque vous le pouvez, car il provoque généralement des "test de la reproduction", et en conséquence, beaucoup plus de tests que nécessaire cassera lorsque les besoins changent. Les utiliser que comme un dernier recours. Option 1 et 2 sont les moyens les plus rapides pour "tester les méthodes privées" ici et maintenant (comme dans la manière la plus rapide à mettre en œuvre), mais ils vont vraiment nuire à la productivité à long terme.

PIMPL peut faire sens, mais elle permet tout de même pour certains assez mauvaise conception. Être prudent avec elle.

Je vous recommande l'Option 4 (refactoring en plus petits composants testables) comme le bon endroit pour commencer, mais parfois, ce que vous voulez vraiment est l'Option 5 (tester les fonctions privées par le biais de l'interface publique).

P. S. Voici pertinentes de la conférence au sujet de l'iceberg classes: https://www.youtube.com/watch?v=4cVZvoFGJTU

P. S. S. Comme pour tout logiciel, la réponse est: ça dépend. Il n'est pas one size fits all. L'option qui permet de résoudre votre problème dépendra de votre spécifique circonstances.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X