Je discutais récemment avec des amis de la meilleure des deux méthodes suivantes pour renvoyer les résultats ou les appels de méthodes dans la même classe à partir de méthodes dans la même classe.
Ceci est un exemple très simplifié. En réalité, les fonctions sont beaucoup plus complexes.
Exemple:
public class MyClass
{
public bool FunctionA()
{
return FunctionB() % 2 == 0;
}
protected int FunctionB()
{
return new Random().Next();
}
}
Donc, pour tester cela, nous avons 2 méthodes.
Méthode 1: utilisez des fonctions et des actions pour remplacer la fonctionnalité des méthodes. Exemple:
public class MyClass
{
public Func<int> FunctionB { get; set; }
public MyClass()
{
FunctionB = FunctionBImpl;
}
public bool FunctionA()
{
return FunctionB() % 2 == 0;
}
protected int FunctionBImpl()
{
return new Random().Next();
}
}
[TestClass]
public class MyClassTests
{
private MyClass _subject;
[TestInitialize]
public void Initialize()
{
_subject = new MyClass();
}
[TestMethod]
public void FunctionA_WhenNumberIsOdd_ReturnsTrue()
{
_subject.FunctionB = () => 1;
var result = _subject.FunctionA();
Assert.IsFalse(result);
}
}
Méthode 2: Rendre les membres virtuels, dériver une classe et dans une classe dérivée, utiliser Fonctions et actions pour remplacer des fonctionnalités Exemple:
public class MyClass
{
public bool FunctionA()
{
return FunctionB() % 2 == 0;
}
protected virtual int FunctionB()
{
return new Random().Next();
}
}
public class TestableMyClass
{
public Func<int> FunctionBFunc { get; set; }
public MyClass()
{
FunctionBFunc = base.FunctionB;
}
protected override int FunctionB()
{
return FunctionBFunc();
}
}
[TestClass]
public class MyClassTests
{
private TestableMyClass _subject;
[TestInitialize]
public void Initialize()
{
_subject = new TestableMyClass();
}
[TestMethod]
public void FunctionA_WhenNumberIsOdd_ReturnsTrue()
{
_subject.FunctionBFunc = () => 1;
var result = _subject.FunctionA();
Assert.IsFalse(result);
}
}
Je veux savoir qui est meilleur et aussi POURQUOI?
Mise à jour: NOTE: FunctionB peut aussi être public
c#
design
unit-testing
tranceru1
la source
la source
FunctionA
retourne un bool mais ne définit qu'une variable localex
et ne retourne rien.public static
mais dans une classe différente.FunctionB
est cassé par la conception.new Random().Next()
est presque toujours faux. Vous devriez injecter l'instance deRandom
. (Random
c'est aussi une classe mal conçue, ce qui peut causer quelques problèmes supplémentaires)Réponses:
Édité après la mise à jour de l’affiche originale.
Avertissement: pas un programmeur C # (principalement Java ou Ruby). Ma réponse serait: je ne le testerais pas du tout et je ne pense pas que vous devriez le faire.
La version la plus longue est la suivante: les méthodes privées / protégées ne font pas partie de l'API, ce sont essentiellement des choix d'implémentation que vous pouvez décider de réviser, de mettre à jour ou de supprimer, sans aucun impact sur l'extérieur.
Je suppose que vous avez un test sur FunctionA (), qui est la partie de la classe visible du monde extérieur. Il devrait être le seul à avoir un contrat à mettre en œuvre (et qui pourrait être testé). Votre méthode privée / protégée n'a pas de contrat à remplir et / ou à tester.
Voir une discussion connexe ici: https://stackoverflow.com/questions/105007/should-i-test-private-methods-or-only-public-ones
Après le commentaire , si FunctionB est public, je vais simplement tester les deux en utilisant le test unitaire. Vous pouvez penser que le test de FunctionA n’est pas totalement "unitaire" (comme il s’appelle FunctionB), mais cela ne me préoccupe pas trop: si le test FunctionB fonctionne mais pas le test FunctionA, cela signifie clairement que le problème ne se trouve pas dans la liste sous-domaine de FunctionB, ce qui est assez bon pour moi en tant que discriminateur.
Si vous voulez vraiment pouvoir séparer totalement les deux tests, je voudrais utiliser une sorte de technique de moquage pour se moquer de FunctionB lors du test de FunctionA (en général, renvoyer une valeur correcte connue fixe). Il me manque la connaissance de l'écosystème C # pour conseiller une bibliothèque moqueuse spécifique, mais vous pouvez vous pencher sur cette question .
la source
MyClass
modifier la conception de la classe de base, consiste à sous-classer et à remplacer la méthode par la fonctionnalité que vous souhaitez remplacer. Il pourrait également être judicieux de mettre à jour votre question pour y inclure des informationsFunctionB
publiques.protected
les méthodes font partie de la surface publique d'une classe, sauf si vous vous assurez qu'il ne peut y avoir d'implémentation de votre classe dans des assemblys différents.Je souscris à la théorie selon laquelle si une fonction est importante à tester ou à remplacer, il est suffisamment important pour ne pas être un détail d'implémentation privée de la classe testée, mais un détail d'implémentation publique d'une classe différente .
Donc, si je suis dans un scénario où j'ai
Ensuite, je vais refactorer.
Maintenant, j'ai un scénario où D () est testable indépendamment et entièrement remplaçable.
En tant que moyen d’organisation, mon collaborateur peut ne pas vivre au même niveau d’espace de nommage. Par exemple, si
A
est dans FooCorp.BLL, mon collaborateur peut être une couche plus profonde, comme dans FooCorp.BLL.Collaborators (ou le nom approprié). Mon collaborateur peut également être visible à l'intérieur de l'assembly via leinternal
modificateur d'accès, que je voudrais également exposer à mes projets de test d'unité via l'InternalsVisibleTo
attribut d'assembly. La conclusion est que vous pouvez toujours garder votre API propre en ce qui concerne les appelants tout en produisant un code vérifiable.la source
Ajoutant à ce que Martin souligne,
Si votre méthode est privée / protégée, ne la testez pas. Il est interne à la classe et ne doit pas être accessible en dehors de la classe.
Dans les deux approches que vous avez mentionnées, j'ai ces préoccupations ...
Méthode 1 - Cela change réellement le comportement de la classe sous test dans le test.
Méthode 2 - Cela ne teste pas le code de production, mais une autre implémentation.
Dans le problème énoncé, je vois que la seule logique de A est de voir si la sortie de FunctionB est paire. Bien que illustratif, FunctionB donne une valeur aléatoire, difficile à tester.
Je m'attends à un scénario réaliste dans lequel nous pouvons configurer MyClass de manière à connaître le retour de FunctionB. Alors que notre résultat attendu est connu, nous pouvons appeler FunctionA et affirmer le résultat réel.
la source
protected
est presque le même quepublic
. Seulementprivate
etinternal
sont des détails de mise en œuvre.internal protected
, utiliser un assistant de réflexion privé ou créer une classe dérivée dans votre projet de test.Personnellement, j’utilise Method1, c’est-à-dire que toutes les méthodes sont transformées en actions ou en fonctions car cela a considérablement amélioré la testabilité du code pour moi. Comme pour toute solution, cette approche présente des avantages et des inconvénients:
Avantages
Les inconvénients
Donc, pour résumer, utiliser Funcs et Actions for Unit est très utile si vous savez que vos classes ne seront jamais remplacées.
De plus, je ne crée généralement pas de propriétés pour Funcs, mais les insère directement en tant que telles.
J'espère que cela t'aides!
la source
Utiliser Mock, c'est possible. Nuget: https://www.nuget.org/packages/moq/
Et croyez-moi, c'est assez simple et logique.
Mock a besoin de la méthode virtuelle à remplacer.
la source