J'écris un analyseur et dans le cadre de cela, j'ai une Expander
classe qui "développe" une seule instruction complexe en plusieurs instructions simples. Par exemple, cela développerait ceci:
x = 2 + 3 * a
dans:
tmp1 = 3 * a
x = 2 + tmp1
Maintenant, je réfléchis à la façon de tester cette classe, en particulier comment organiser les tests. Je pouvais créer manuellement l'arbre de syntaxe d'entrée:
var input = new AssignStatement(
new Variable("x"),
new BinaryExpression(
new Constant(2),
BinaryOperator.Plus,
new BinaryExpression(new Constant(3), BinaryOperator.Multiply, new Variable("a"))));
Ou je pourrais l'écrire sous forme de chaîne et l'analyser:
var input = new Parser().ParseStatement("x = 2 + 3 * a");
La deuxième option est beaucoup plus simple, plus courte et plus lisible. Mais il introduit également une dépendance sur Parser
, ce qui signifie qu'un bogue dans Parser
pourrait échouer ce test. Donc, le test cesserait d'être un test unitaire de Expander
, et je suppose que techniquement devient un test d'intégration de Parser
et Expander
.
Ma question est: est-il acceptable de s'appuyer principalement (ou complètement) sur ce type de tests d'intégration pour tester cette Expander
classe?
Parser
puisse échouer dans un autre test n'est pas un problème si vous ne vous engagez habituellement qu'à zéro échec, au contraire, cela signifie que vous avez plus de couvertureParser
. Ce qui m'inquiète plutôt, c'est qu'un bogue dansParser
pourrait faire réussir ce test alors qu'il aurait dû échouer . Les tests unitaires sont là pour trouver des bugs, après tout - un test est cassé quand il ne le fait pas mais aurait dû.Réponses:
Vous allez vous retrouver à écrire beaucoup plus de tests, de comportement beaucoup plus compliqué, intéressant et utile, si vous pouvez le faire simplement. Donc, l'option qui implique
est tout à fait valable. Cela dépend d'un autre composant. Mais tout dépend de dizaines d'autres composants. Si vous vous moquez de quelque chose à un pouce de sa vie, vous dépendez probablement de nombreuses fonctionnalités de moquerie et de tests.
Les développeurs se concentrent parfois trop sur la pureté de leurs tests unitaires , ou développent des tests unitaires et des tests unitaires uniquement , sans module, intégration, stress ou autres types de tests. Tous ces formulaires sont valides et utiles, et ils relèvent tous de la responsabilité des développeurs - pas seulement du Q / A ou du personnel d'exploitation plus loin dans le pipeline.
Une approche que j'ai utilisée consiste à commencer par ces exécutions de niveau supérieur, puis à utiliser les données produites à partir de celles-ci pour construire l'expression longue du plus petit dénominateur commun du test. Par exemple, lorsque vous videz la structure de données de ce qui est
input
produit ci-dessus, vous pouvez facilement construire:sorte de test qui teste au niveau le plus bas. De cette façon, vous obtenez un bon mélange: une poignée des tests primitifs les plus élémentaires (tests unitaires purs), mais vous n'avez pas passé une semaine à écrire des tests à ce niveau primitif. Cela vous donne le temps nécessaire pour écrire beaucoup plus, un peu moins de tests atomiques en utilisant le
Parser
comme assistant. Résultat final: plus de tests, plus de couverture, plus de coins et d'autres cas intéressants, un meilleur code et une assurance qualité plus élevée.la source
Bien sûr, c'est OK!
Vous avez toujours besoin d'un test fonctionnel / d'intégration qui utilise le chemin de code complet. Et le chemin de code complet dans ce cas signifie inclure l'évaluation du code généré. C'est-à-dire que vous testez que l'analyse
x = 2 + 3 * a
produit du code qui, s'il est exécuté avec,a = 5
sera définix
sur17
et s'ila = -2
sera exécuté avec , définix
sur-4
.En dessous, vous devez faire des tests unitaires pour des bits plus petits tant que cela aide réellement à déboguer le code . Les tests les plus fins que vous aurez, la probabilité plus élevée que toute modification du code devra également changer le test, car l'interface interne change. Ces tests ont peu de valeur à long terme et ajoutent des travaux de maintenance. Il y a donc un point de rendements décroissants et vous devez vous arrêter avant.
la source
Les tests unitaires vous permettent de localiser des éléments spécifiques qui se cassent et où ils se sont cassés dans le code. Ils sont donc bons pour les tests à grains très fins. De bons tests unitaires aideront à réduire le temps de débogage.
Cependant, d'après mon expérience, les tests unitaires sont rarement assez bons pour vérifier le bon fonctionnement. Les tests d'intégration sont donc également utiles pour vérifier une chaîne ou une séquence d'opérations. Les tests d'intégration vous aident à passer au travers des tests fonctionnels. Comme vous l'avez cependant souligné, en raison de la complexité des tests d'intégration, il est plus difficile de trouver l'endroit spécifique dans le code où le test se casse. Il a également un peu plus de fragilité en ce sens que des échecs n'importe où dans la chaîne entraîneront l'échec du test. Cependant, vous aurez toujours cette chaîne dans le code de production, donc tester la chaîne réelle est toujours utile.
Idéalement, vous auriez les deux, mais en tout cas, avoir un test automatisé est généralement mieux que de ne pas avoir de test.
la source
Faites beaucoup de tests sur l'analyseur et lorsque l'analyseur réussit les tests, enregistrez ces sorties dans un fichier pour simuler l'analyseur et tester l'autre composant.
la source