J'écris un analyseur et en tant que partie de cela, j'ai une classe Expander
qui "élargit" une instruction complexe unique dans plusieurs déclarations simples. Par exemple, cela développerait ceci:
x = 2 + 3 * a
dans:
tmp1 = 3 * a
x = 2 + tmp1
Maintenant, je pense à la manière de tester cette classe, notamment comment organiser les tests. Je pourrais créer manuellement l'arborescence de la 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 comme une chaîne et analyser:
var input = new Parser().ParseStatement("x = 2 + 3 * a");
La deuxième option est beaucoup plus simple, plus courte et lisible. Mais il introduit également une dénonpérance 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 techniquement devenir un test d'intégration de Parser
et Expander
.
Ma question est la suivante: est-ce que ça va de compter surtout (ou complètement) sur ce type d'essai d'intégration pour tester cette classe Expander
classe?
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
var input = new Parser().ParseStatement("x = 2 + 3 * a");
est assez valable. Cela dépend d'un autre composant. Mais Tout dépend de des dizaines d'autres composants. Si vous vous moquez de quelque chose à un pouce de sa vie, vous dépendez probablement de nombreuses fonctionnalités moqueuses et des appareils de test.
Les développeurs parfois se concentrent sur la pureté de leurs tests unitaires ou développant des tests d'unités et des tests d'unité uniquement , sans aucun module , intégration, stress ou autres types de tests. Toutes ces formes sont valables et utiles, et ils sont toutes les responsabilités appropriées des développeurs - pas seulement le personnel Q/A ou des opérations davantage sur le pipeline.
Une approche que j'ai utilisée est de commencer avec ces exécutions de niveau supérieur, puis utilisez les données produites à partir d'eux pour construire la forme longue, la plus faible expression-dénominatrice du test. Par exemple. Lorsque vous videz la structure de données du input
produit ci-dessus, vous pouvez facilement construire le:
var input = new AssignStatement(
new Variable("x"),
new BinaryExpression(
new Constant(2),
BinaryOperator.Plus,
new BinaryExpression(new Constant(3), BinaryOperator.Multiply, new Variable("a"))));
type 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 fondamentaux (tests de l'unité pure), mais n'a pas passé de semaine à écrire des tests à ce niveau primitif. Cela vous donne la ressource temporelle nécessaire pour écrire de nombreux autres tests atomiques légèrement moins atomiques utilisant le Parser
comme assistant. Résultat final: plus de tests, plus de couverture, plus d'angle et d'autres cas intéressants, de meilleurs codes et une assurance qualité supérieure.
Bien sûr que ça va!
Vous avez toujours besoin de test fonctionnel/d'intégration qui exercent le chemin complet du code. Et Terminez le trajet de code dans ce cas, y compris l'évaluation du code généré. C'est vous test que l'analyse x = 2 + 3 * a
produit du code que s'il est exécuté avec a = 5
définira x
à 17
et si couramment avec a = -2
définira x
à -4
.
En dessous de cela, vous devriez faire des tests d'unités pour des bits plus petits tant que cela aide à déboguer le code. Les tests de grains plus fins que vous aurez, la probabilité plus élevée que toute modification du code devra également modifier le test, car l'interface interne change. Un tel test a peu de valeur à long terme et ajoutez des travaux de maintenance. Donc, il y a un point de rendement décroissant et vous devriez arrêter avant cela.
Les tests d'unités vous permettent d'épingler les éléments spécifiques du point qui se brisent et où dans le code, ils ont cassé. Donc, ils sont bons pour des tests à grains très fins. De bons tests unitaires aideront à réduire le temps de débogage.
Toutefois, de mes tests d'unité d'expérience sont rarement suffisamment 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 font faire partie du processus fonctionnel. Comme vous l'avez souligné cependant, en raison de la complexité des tests d'intégration, il est plus difficile de trouver l'endroit spécifique du code où le test se casse. Il a également un peu plus de fragilité dans ces échecs où que ce soit dans la chaîne entraînera l'échec du test. Vous aurez toujours cette chaîne dans le code de production, cependant, de tester la chaîne actuelle est toujours utile.
Idéalement, vous auriez les deux, mais en tout cas, il est généralement préférable d'utiliser un test automatisé que d'avoir aucun test.
Faites beaucoup de tests sur l'analyseur et que l'analyseur passe les tests, enregistrez ces sorties dans un fichier pour se moquer de l'analyseur et testez l'autre composant.