Rendre le code interne mais disponible pour les tests unitaires à partir d'autres projets

129

Nous mettons tous nos tests unitaires dans leurs propres projets. Nous constatons que nous devons rendre certaines classes publiques au lieu d'interne juste pour les tests unitaires. Y a-t-il de toute façon pour éviter d'avoir à faire cela. Quelle est l'implication de la mémoire en rendant les classes publiques au lieu de scellées?

Léora
la source
2
duplication possible du modificateur d'accès "interne" C # lors de tests unitaires
Dour High Arch

Réponses:

205

Si vous utilisez .NET, l' attribut d'assembly InternalsVisibleTo vous permet de créer des assemblys "amis". Il s'agit d'assemblys spécifiques fortement nommés qui sont autorisés à accéder aux classes internes et aux membres de l'autre assembly.

Notez que cela doit être utilisé avec discrétion car il couple étroitement les assemblys impliqués. Une utilisation courante d'InternalsVisibleTo est pour les projets de test unitaire. Ce n'est probablement pas un bon choix pour une utilisation dans vos assemblys d'application réels, pour la raison indiquée ci-dessus.

Exemple:

[assembly: InternalsVisibleTo("NameAssemblyYouWantToPermitAccess")]
namespace NameOfYourNameSpace
{
Cendre
la source
23
Je suggère de mettre un #if DEBUG autour de l'attribut, puis des tests unitaires dans le débogage. De cette façon, vous serez sûr que l'attribut n'est pas défini sur le code de version.
steve cook le
C'est juste une idée, je ne sais pas .... Que diriez-vous de: #if DEBUG classe publique IniReader #else classe interne IniReader #endif Probablement pas recommandé? Pourquoi?
jmelhus
4
Eh bien, pourquoi limiter les tests aux versions de débogage?
Marco Mp
2
Aussi, juste un pinaillage, il n'est pas nécessaire que les assemblages "amis" soient fortement nommés (cela pourrait être une bonne pratique - même si mon goût personnel dit le contraire, mais pas obligatoire).
Marco Mp
9
Être en désaccord. Si je construis un composant complexe avec une API publique très fine, il n'est ni pratique ni réaliste de tester uniquement via l'API publique. Vous vous retrouverez avec une boule de boue impossible à entretenir. Au lieu de cela, je définirais soigneusement les unités internes et les testerais séparément. Comme Jeremy D. Miller l'a dit assez souvent: "Tester petit avant de tester grand".
Dennis Doomen
6

S'il s'agit d'une classe interne, elle ne doit pas être utilisée de manière isolée. Par conséquent, vous ne devriez pas vraiment le tester en dehors du test d'une autre classe qui utilise cet objet en interne.

Tout comme vous ne devriez pas tester les membres privés d'une classe, vous ne devriez pas tester les classes internes d'une DLL. Ces classes sont des détails d'implémentation d'une classe accessible au public, et doivent donc être bien utilisées à travers d'autres tests unitaires.

L'idée est que vous ne voulez tester que le comportement d'une classe car si vous testez les détails de l'implémentation interne, vos tests seront fragiles. Vous devriez pouvoir modifier les détails d'implémentation de n'importe quelle classe sans interrompre tous vos tests.

Si vous trouvez que vous avez vraiment besoin de tester cette classe, vous voudrez peut-être réexaminer pourquoi cette classe est interne en premier lieu.

Josh
la source
2
Les détails de la mise en œuvre doivent être exercés dans le cadre d'un test global. Ne regardez pas les variables privées ... testez le comportement attendu. Si le test est correct ... toute la plomberie et le câblage internes doivent être testés dans le cadre de celui-ci. Voté.
Gishu
69
Je ne suis pas nécessairement d'accord avec cela car ces classes sont "publiques" pour d'autres classes à l'intérieur de la DLL et la fonctionnalité de la classe doit être testée indépendamment
leora
27
Je ne suis pas d'accord non plus. Les unités sont des unités et elles doivent être testées isolément.
Sentinel
5
Excellente directive générale, mais ne soyons pas dogmatiques. Les tests ont deux objectifs principaux: 1) Régression - assurez-vous de ne pas casser quelque chose, 2) Augmenter la vitesse de développement. Si je dois mettre en place un énorme service à chaque fois que je veux écrire une ligne de code, j'entraverai le développement. Si j'ai une pièce interne complexe, je veux pouvoir développer et tester de manière isolée. Inversement, je ne veux pas que tout soit testé isolément (diminuant ainsi la valeur du test de régression ou dupliquant le code de test), mais certains codes justifient l'isolement. Peut-être que la clé est d'en faire un assemblage séparé.
Lee Jensen
2
OP ici est correct. Vous couplez vos tests à l'implémentation interne. Par conséquent, votre équipe est à jamais esclave de la réparation des tests et de l'horrible code moqueur. Testez uniquement les API publiques, qu'il s'agisse de cette API de bibliothèque intégrée ou d'API exposées au réseau. Tout le code doit pouvoir être exercé via la porte d'entrée. Tester la mise en œuvre est la raison pour laquelle TDD est en grande partie mort, c'est juste un énorme PITA pour tester littéralement chaque classe de l'application entière, plutôt que de se concentrer sur l'assurance des comportements du système. Je pense que presque tout le monde, y compris les livres «faisant autorité», a eu tort ou pas clairement.
Luke Puplett
4

à des fins de documentation

vous pouvez également instancier une classe interne en utilisant la Type.GetTypeméthode

exemple

//IServiceWrapper is public class which is 
//the same assembly with the internal class 
var asm = typeof(IServiceWrapper).Assembly;
//Namespace.ServiceWrapper is internal
var type = asm.GetType("Namespace.ServiceWrapper");
return (IServiceWrapper<T>)Activator
    .CreateInstance(type, new object[1] { /*constructor parameter*/ });

pour le type générique, il existe différents processus comme ci-dessous:

var asm = typeof(IServiceWrapper).Assembly;
//note the name Namespace.ServiceWrapper`1
//this is for calling Namespace.ServiceWrapper<>
var type = asm.GetType("Namespace.ServiceWrapper`1");
var genType = type.MakeGenericType(new Type[1] { typeof(T) });
return (IServiceWrapper<T>)Activator
     .CreateInstance(genType, new object[1] { /*constructor parameter*/});
ktoutnik
la source
-5

Les classes peuvent être à la fois publiques ET scellées.

Mais ne fais pas ça.

Vous pouvez créer un outil pour réfléchir sur les classes internes et émettre une nouvelle classe qui accède à tout via la réflexion. MSTest fait cela.

Edit: Je veux dire, si vous ne voulez pas inclure -any- des éléments de test dans votre assemblage d'origine; cela fonctionne également si les membres sont privés.

TraumatismePoney
la source
1
Attends quoi? Vous dites ne pas en faire public sealed class? Quel est votre raisonnement pour ce joyau?
écraser
@crush traumapony n'est pas connecté depuis 2009.
Prof. Falken