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:
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:
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:
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.