J'ai récemment discuté avec des amis de laquelle des 2 méthodes suivantes est préférable de stub renvoyer les résultats ou les appels aux méthodes à l'intérieur de la même classe à partir de méthodes à l'intérieur de 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 la classe et dans la classe dérivée utiliser des fonctions et des actions pour remplacer la fonctionnalité 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 ce qui est mieux et aussi POURQUOI?
Mise à jour: REMARQUE: FunctionB peut également être public
Modifié 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.
La version 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 revoir, de mettre à jour ou de supprimer complètement 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. Ce 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 aucun 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 je ne serais pas trop inquiet par cela: si le test FunctionB fonctionne mais pas le test FunctionA, cela signifie clairement que le problème n'est pas dans le sous-domaine de FunctionB, ce qui est assez bon pour moi en tant que discriminateur.
Si vous voulez vraiment être en mesure de séparer totalement les deux tests, j'utiliserais une sorte de technique de simulation pour se moquer de FunctionB lors du test de FunctionA (généralement, retourne une valeur correcte connue fixe). Je n'ai pas les connaissances de l'écosystème C # pour conseiller une bibliothèque de moquerie spécifique, mais vous pouvez regarder cette question .
Je souscris à la théorie selon laquelle si une fonction est importante à tester ou importante à remplacer, elle est suffisamment importante pour ne pas être un détail d'implémentation privé de la classe testée, mais pour être un détail d'implémentation publique d'un différent classe.
Donc, si je suis dans un scénario où j'ai
class A
{
public B C()
{
D();
}
private E D();
{
// i actually want to control what this produces when I test C()
// or this is important enough to test on its own
// and, typically, both of the above
}
}
Ensuite, je vais refactoriser.
class A
{
ICollaborator collaborator;
public A(ICollaborator collaborator)
{
this.collaborator = collaborator;
}
public B C()
{
collaborator.D();
}
}
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 pourrait ne vit pas au même niveau d'espace de noms. Par exemple, si A
se trouve dans FooCorp.BLL, alors mon collaborateur peut avoir une autre couche de profondeur, comme dans FooCorp.BLL.Collaborators (ou tout autre nom approprié). Mon collaborateur pourrait en outre être uniquement visible à l'intérieur de l'assembly via le modificateur d'accès internal
, que j'exposerais ensuite à mes projets de test unitaire via l'attribut InternalsVisibleTo
Assembly. La conclusion est que vous pouvez toujours garder votre API propre, en ce qui concerne les appelants, tout en produisant du code vérifiable.
Personnellement, j'utilise la méthode 1, c'est-à-dire la transformation de toutes les méthodes en actions ou en fonctions, car cela a énormément 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 pour le test unitaire est génial 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 je les insère directement en tant que telles
public class MyClass
{
public Func<int> FunctionB = () => new Random().Next();
public bool FunctionA()
{
return FunctionB() % 2 == 0;
}
}
J'espère que cela t'aides!
Pour ajouter à 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 mentionnez, j'ai ces préoccupations -
Méthode 1 - Cela modifie en fait le comportement de la classe sous test dans le test.
Méthode 2 - En fait, cela ne teste pas le code de production, mais teste 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 qu'illustratif, FunctionB donne une valeur aléatoire, ce qui est difficile à tester.
Je m'attends à un scénario réaliste où nous pouvons configurer MyClass de telle sorte que nous sachions ce que FunctionB retournerait. Ensuite, notre résultat attendu est connu, nous pouvons appeler FunctionA et affirmer le résultat réel.