11
votes

Comment passer un code de test très complexe derrière l'interface publique

Je me demande comment je devrais tester ce type de fonctionnalité via Nunit.

Public void HighlyComplexCalculationOnAListOfHairyObjects()
{
    // calls 19 private methods totalling ~1000 lines code + comments + whitespace
}


5 commentaires

Comme d'autres l'ont souligné: si une classe est si difficile à tester, c'est souvent un signe que la classe tente de faire trop. Si le calcul peut être décomposé en méthodes plus petites, est-il possible de la rompre plus loin dans des étapes séparées, à chaque étape représentée par sa propre classe / interface? Ou, si le calcul consiste principalement en un groupe de formules complexes, vous pouvez avoir une classe spécialisée maths -itant avec un tas de méthodes statiques mettant en œuvre chaque formule.


Non sans un degré de ré-factorisation qui est en dehors de la portée / budget / temps actuel.


Dan - Qu'avez-vous fini par faire? Nous avons les mêmes problèmes.


J'ai fini par liaison au cadre de test Visual Studio et en ajoutant "Utilisation de SivniteObject = Microsoft.VisualStudio.TestTools.UnittestSting.privateObject;" à mon projet de test afin que je puisse accéder aux membres privés sans écrire ma propre classe de réflexion à le faire. J'ai ensuite ajouté un certain nombre de tests de méthodes privées à bas niveau au bas de l'arborescence appelant pour remplir les niveaux de niveau élevé en exécutant la méthode publique de niveau supérieur. À l'heure actuelle, il n'y a pas de test qui couvre explicitement les méthodes de niveau moyen. Je pourrais les ajouter plus tard, mais la quantité de configuration nécessaire pour créer des intrants est prohibitive.


Comme indiqué ci-dessous, ce n'est pas l'option idéale, mais des fonds pour une refonte majeure n'existent pas, donc je fais ce que je peux maintenant. Les améliorations futures des tests seront envisagées lorsque des fonds supplémentaires sont disponibles ou en réponse à tout problème spécifique identifié.


9 Réponses :



8
votes

Vous avez enflé deux choses. L'interface (qui pourrait exposer très peu) et cette classe de mise en œuvre particulière qui pourrait exposer beaucoup plus.

  1. définir l'interface la plus étroite possible.

  2. Définissez la classe de mise en œuvre avec des méthodes et des attributs testables (non privés). Ça va bien si la classe a des choses "supplémentaires".

  3. Toutes les applications doivent utiliser l'interface et, par conséquent, n'ont pas d'accès de type à sécurité aux fonctionnalités exposées de la classe.

    Et si "quelqu'un" contourne l'interface et utilise directement la classe? Ils sont des sociopathes - vous pouvez les ignorer en toute sécurité. Ne leur fournissez pas de support téléphonique car ils ont violé la règle fondamentale d'utiliser l'interface et non de la mise en œuvre.


4 commentaires

Exposer des méthodes pour faciliter les tests ne se sent pas bien.


@Dan NEELY: (1) C'est pourquoi c'est le test conçu . Vous avez à peu près à. Plus important. (2) C'est pourquoi l'interface et la mise en œuvre sont séparées. L'interface n'expose rien. Et (3) dans Python, nous ne dérangons pas beaucoup avec "Private" et nous sommes parfaitement heureux et réussis. "Exposition" ne pas - à long terme - comptez beaucoup.


@S. Lott - une question, comment cela aide-t-il si vos méthodes précédemment appellent d'autres méthodes privées? Même si vous faites tous ces publics, vous n'avez pas d'interface pour eux, vous ne pouvez donc pas les moquer (sauf si vous les faites aussi virtuelle, ce qui semble un peu étrange). Des pensées?


La mise en œuvre (pas l'interface) n'a peu d'utilisations pour les méthodes privées. En outre, la mise en œuvre présente également des méthodes qui sont l'API «officielle» - la partie testable - distincte de plus de détails sur la mise en œuvre privés. Vous n'avez pas à tester chaque méthode . Il vous suffit de tester les méthodes qui sont l'interface "officielle". Si vous ne pouvez pas distinguer, supprimez-vous avec privé et utilisez l'interface pour fournir toute la confidentialité dont vous avez vraiment besoin.



1
votes

J'ai vu (et probablement écrit) beaucoup d'objets de cheveux. S'il est difficile de tester, c'est généralement un bon candidat pour refactoring. Bien sûr, un problème avec c'est que la première étape de refactoring est en train de s'assurer qu'elle passe d'abord tous les tests.

Honnêtement, cependant, je chercherais à voir s'il n'y a pas une manière dont vous pouvez casser ce code dans une section plus gérable.


0 commentaires

0
votes

Votre question implique qu'il existe de nombreux chemins d'exécution dans tout le sous-système. La première idée qui apparaît à l'esprit est "Refactor". Même si votre API reste une interface à une méthode, les tests ne doivent pas être «impossibles».


0 commentaires

2
votes

Personnellement, je ferais personnellement les méthodes constitutives internes, appliquer internalsvisibleto et tester les différents bits.

Les tests d'unités de boîte blanche peuvent toujours être efficaces - bien que cela soit généralement plus fragile que le test de boîte noire (c'est-à-dire que vous êtes plus susceptible de devoir modifier les tests si vous modifiez la mise en œuvre).


1 commentaires

Le code a été autour et dans la production qui sauf des bugs découverts que le changement est peu probable à ce stade. Au niveau de l'ondulation des mains, l'algorithme fait quelque chose de similaire à la compression de la perte en fonction de la mise en œuvre, il existe plus d'une sortie possible pour une entrée donnée qui serait «correcte» et que tout changement majeur. En conséquence, tous les changements majeurs briseraient probablement les tests Blackbox.



2
votes

HautComplexCalcultonalistofhairyObjects () est une odeur de code, une indication que la classe qui contient cela est potentiellement trop nombreuse et doit être refactored via Classe d'extraction . Les méthodes de cette nouvelle classe seraient publiques et donc testables comme des unités.

Un problème à un tel refactoring est que la classe d'origine a tenu beaucoup d'état que la nouvelle classe aurait besoin. Qui est une autre odeur de code, une personne qui indique que l'état doit être déplacé dans un objet de valeur.


0 commentaires


0
votes

essayer de créer un ensemble de données de test qui a pleinement exécuté tout le fonctionnalité impliquée dans le le calcul serait presque impossible

Si c'est vrai, essayez un objectif moins ambitieux. Commencez par tester des chemins spécifiques à l'utilisation élevée grâce au code, les chemins que vous soupçonnez peuvent être fragiles et des chemins pour lesquels vous avez signalé des bugs.

refactoring La méthode dans des sous-algorithmes distincts rendra votre code plus testable (et pourrait être bénéfique d'une autre manière), mais si votre problème est un nombre ridicule d'interactions entre ces sous-algorithmes, La méthode d'extraction (ou extraire de la classe de stratégie) ne le résoudra pas vraiment: vous devrez construire une suite solide de tests une à la fois.


1 commentaires

Dans une certaine mesure, nous l'avons déjà. Tous les cas communs sont couverts par un ensemble de fichiers de test pour un test d'acceptation axé sur l'homme; Et l'un de mes collègues est (AB) en utilisant Nunit pour créer un ensemble de tests de niveau d'intégration qui chargent, traite et examine la sortie des fichiers de test. En raison de la complexité de ce qui se passe à l'intérieur de la poussée principale consiste à tester les méthodes internes séparément pour vous assurer que nous n'avons pas manqué de cas de bord. Pour mes péchés (avoir fait le VBA-> C # portuaire il y a des années), et avoir la meilleure idée de ce qui se passe à l'intérieur de la deuxième partie est tombé à moi.