Dummy vs Stub vs Spy vs Fake vs Mock

De Wiki Agile du @GroupeCESI
Aller à : navigation, rechercher

Auteur : Niraj Bhatt (@nirajrules)
Source : Dummy vs. Stub vs. Spy vs. Fake vs. Mock
Date : 27/08/2012


Traducteur : Fabrice Aimetti
Date : 16/12/2012
Merci à : Thomas Diebolt, Bruno Orsier


Traduction :

L'une des exigences fondamentales à respecter lorsqu'on écrit des tests unitaires, c'est l'isolation. L'isolation est difficile à obtenir dans le monde réel car il y a toujours des dépendances (collaborateurs) avec l'ensemble du système. C'est ici que le concept générique de Doublure de Test (Test Double) entre en jeu. Une doublure (Double) nous permet de briser la dépendance d'origine, nous aidant ainsi à isoler le système en cours de test (SUT, System Under Test). Comme cette doublure est censée passer un test unitaire, ce dernier est généralement dénommé doublure de test. Il y a plusieurs types de doublures de tests, classés selon leurs intentions (ce qui me rappelle le patron de conception du Proxy par le GOF).
Les doublures de tests ne sont pas seulement utiles pour vérifier l'état, mais aussi le comportement ; elles nous aident à améliorer la couverture du code par les tests unitaires. Même si séparer stricto sensu les différents types de doublures de tests ne nous fournit pas une valeur ajoutée exceptionnelle, les connaître va très certainement nous aider à organiser notre réflexion autour des tests unitaires. Des frameworks de Simulacres (Mocks) sont aujourd'hui disponibles et nous permettent de créer de façon transparente toutes les variantes de doublures de tests. J'utiliserai moq dans cet article. Les variantes de Doublures de Tests décrites ci-dessous sont extraites de xUnit Patterns.com. Vous trouverez ci-après les différentes variantes de doublures de tests avec des exemples :

a) le Fantôme (Dummy), est la doublure de test la plus simple. Il s'agit d'un espace réservé requis pour passer le test unitaire. Le système en cours de test (SUT) n'utilise pas cet espace réservé. Un fantôme peut être quelque chose d'aussi simple que de passer la valeur 'null' ou une implémentation void avec la gestion de quelques exceptions pour s'assurer qu'il n'a pas d'effet de bord.

[TestMethod]
public void PlayerRollDieWithMaxFaceValue()
{
var dummyBoard = new Mock<IBoard>();
var player = new Player(dummyBoard.Object, new Die() ); //null too would have been just fine
player.RollDie();
Assert.AreEqual(6, player.UnitsToMove);
}

Même si le test ci-dessus pourrait très bien fonctionner, il ne lèvera pas d'exception si l'implémentation de RollDie invoque l'objet Board. Afin de s'assurer que l'objet Board n'est pas du tout appelé, vous pouvez tirer parti d'un simulacre strict (Strict Mock) qui lèvera une exception si aucune attente (Expectation) n'est configurée pour l'élément concerné.

[TestMethod]
public void PlayerRollDieWithMaxFaceValueStrictTest()
{
var dummyBoard = new Mock<IBoard>(MockBehavior.Strict); //Ensure Board class is never invoked
var player = new Player( dummyBoard.Object, new Die() );
player.RollDie();
Assert.AreEqual( 6, player.UnitsToMove );
}


b) le Substitut (Fake) est utilisé pour simplifier une dépendance afin que le test unitaire puisse passer facilement. Il y a une séparation très mince entre le substitut et le bouchon (Stub) qui est très bien décrite ici : "Un bouchon de test (Test Stub) agit comme un point de contrôle permettant d'injecter des entrées indirectes dans le système en cours de test (SUT), ce que le substitut (Fake) ne fait pas. Le substitut fournit simplement une manière de faire pour que les interactions se produisent d'une manière cohérente. Ces interactions (entre le système en cours de test et l'Objet Substitut) seront généralement nombreuses et les valeurs passées comme arguments des appels de méthodes amonts seront souvent retournées comme résultats dans les appels de méthodes avales". Un cas classique d'utilisation du substitut est l'accès aux base de données. L'exemple ci-dessous montre l'utilisation d'un FakeProductRepository à la place de la base de données.

public interface IProductRepository
{
void AddProduct(IProduct product);
IProduct GetProduct(int productId);
}

public class FakeProductRepository : IProductRepository
{
List<IProduct>
_products = new List<IProduct>();
public void AddProduct(IProduct product)
{
//...
}
public IProduct GetProduct(int productId)
{
//...
}
}

[TestMethod]
public void BillingManagerCalcuateTax()
{
var fakeProductRepository = new FakeProductRepository();
BillingManager billingManager = new BillingManager(fakeProductRepository);
//...
}

Les substituts peuvent également être mis en œuvre par moq en passant par des fonctions de rappel (Callbacks).
c) le Bouchon (Stub) est utilisée pour fournir des entrées indirectes au système en cours de test (SUT) et provenant de ses collaborateurs / dépendances. Ces entrées peuvent être sous forme d'objets, d'exceptions ou de valeurs primitives. Contrairement aux substituts (Fake), les bouchons sont utilisés par le SUT. Si l'on revient à l'exemple des Dés, nous pouvons utiliser un Bouchon pour retourner une valeur fixe. Cela peut simplifier nos tests en enlevant le caractère aléatoire associé au lancer de dé.

[TestMethod]
public void PlayerRollDieWithMaxFaceValue()
{
var stubDie = new Mock<IDie>();
stubDie.Setup(d => d.GetFaceValue()).Returns(6).Verifiable();
IDie die = stubDie.Object;
Assert.AreEqual(6, die.GetFaceValue()); //Excercise the return value
}

d) le Simulacre (Mock) : à l'image des entrées indirectes qui sont réinjectées via ses collaborateurs dans le système en cours de test (SUT), il y a également des sorties indirectes. Les sorties indirectes sont difficiles à tester car elles ne sont pas renvoyées au SUT et sont encapsulées par un collaborateur. Par conséquent, il devient très difficile de vérifier des assertions à partir d'un système en cours de test. C'est là que la vérification de comportement entre en jeu. En utilisant la vérification des comportements, nous pouvons définir des attentes (Expectations) pour le système en cours de test et afficher le bon comportement lors de ses interactions avec les collaborateurs. Un exemple classique de ceci est l'authentification. Quand un système en cours de test invoque l'authentifieur, il peut être très difficile pour nous de vérifier une assertion sur l'annuaire de l'authentifieur (fichier, base de données, ...). Mais ce que nous pouvons faire, c'est vérifier que l'authentifieur est invoqué par le système en cours de test. Un exemple illustre ci-dessous un bouchon classique :

[TestMethod]
public void ModuleThrowExceptionInvokesLogger()
{
var mock = new Mock<ILogger>();
Module module = new Module();
ILogger logger = mock.Object;
module.SetLogger(logger);
module.ThrowException("Catch me if you can");
mock.Verify( m => m.Log( "Catch me if you can" ) );
}

e) l'Espion (Spy) est une variante de la vérification de comportement. Au lieu de créer des attentes (Expectations) sur le comportement, l'espion enregistre les appels vers le collaborateur. Le système en cours de test (SUT) peut alors vérifier plus tard des assertions à partir de l'historique enregistré par l'espion. L'exemple ci-dessous est une variante de l'authentifieur utilisé pour le simulacre (Mock). Le test consiste à compter le nombre de fois où l'authentification est invoquée par l'authentifieur. On ne se soucie pas des entrées passées pour s'authentifier, on enregistre seulement l'historique des appels et on vérifie des assertions sur ces appels. Les espions sur des objets complexes peuvent également tirer parti des fonctions de rappel (Callbacks) dans le framework moq.

[TestMethod]
public void ModuleThrowExceptionInvokesLoggerOnlyOnce()
{
var spyLogger = new Mock<ILogger>();
Module module = new Module();
ILogger logger = spyLogger.Object;
module.SetLogger( logger );
module.ThrowException( "Catch me if you can" );
module.ThrowException( "Catch me if you can" );
spyLogger.Verify( m => m.Log( It.IsAny<string>()), Times.Exactly(2) );
}

J'espère que cet article vous aidera !!!