Accueil > Visual Studio > Tester du code intestable grâce à MS Fakes

Tester du code intestable grâce à MS Fakes

Tous les développeurs qui ont eu à coder des tests unitaires (c’est à dire tous les développeurs du monde sur chacune de ses lignes de code dans un monde idéal^^) on rencontré des problèmes de code non testable: design logiciel qui ne s’y prête pas, forte adhérence à un ou des composants extérieurs,… L’état de l’art du design logiciel aujourd’hui fait à raison une grande place à la testabilité et les éditeurs l’ont bien compris en intégrant ces possibilités directement aux frameworks du marché – Asp.Net MVC en est un bon exemple, quasiment tout est testable sans difficulté grâce à l’injection de dépendances built-in. Mais souvent, nous avons à travailler sur du vieux code qui n’est pas vraiment au goût du jour et donc pas vraiment testable. Heureusement, une nouvelle fonctionnalité apparue dans Visual Studio 2012 va nous permettre de le faire quand même.

MS Fakes permet de générer de faux objets à partir de n’importe quelle assembly référencée dans un projet de test. 2 types d’objets sont générés: des objets Stub et des objets Shim. Les Stubs seront générés pour chaque classe abstraite et chaque interface de l’assembly et permettent de créer des instances de ces classes. Ils sont un gain de temps mais n’apportent pas vraiment de grande valeur ajoutée: il était déjà possible de créer des implémentations de test des ce classes abstraites et interfaces même sans MS Fakes. Les plus intéressants sont donc les Shims.

Ces Shims créent des copies de toutes les classes présentes dans l’assembly cible, même de celles qui n’étaient pas extensibles ou pas instantiables directement. Nous allons démontrer cela à travers un exemple simple. Nous allons tenter de tester cette méthode toute simple:

public class SomeClass
{
    public bool SomeMethod()
    {
        var session = HttpContext.Current.Session;
        if (session["someSessionData"].ToString() == "OK")
            return true;
        return false;               
    }
}

Dès que l’objet HttpSessionState contenu dans le HttpContext courant est utilisé dans une méthode, elle n’est normalement plus testable. Les Shims vont cependant nous permettre de redéfinir dans le contexte de test l’appel à HttpContext.Current et nous pourrons donc redescendre sur l’objet de session en le redéfinissant au sein du fake HttpContext.

Avant tout, il faut générer l’assembly de fakes. Pour cela déroulez les références de votre projet de test, clic droit sur System.Web et sélectionnez « Add Fakes Assembly »

generate fake assembly

Cette « fake » assembly contient les namespaces de l’assembly originale suffixés de .Fakes. La méthde que nous voulons tester ne prend pas d’argument et fait directement référence à HttpContext.Current.Session nous allons donc devoir faker la classe HttpContext, la propriété statique Current de cette classe et la propriété Session de cette instance. Nous allons également devoir simuler l’objet HttpSessionState pour lui permettre de renvoyer une valeur à l’appel session[« someSessionData »]. Nous considérons le test comme passé si la méthode renvoie true. Le test sera le suivant:

[TestMethod]
public void SomeTestMethod()
{
    using (ShimsContext.Create())
    {
        var instanceToTest = new SomeClass();
 
        var session = new System.Web.SessionState.Fakes.ShimHttpSessionState();
        session.ItemGetString = (key) => { if (key == "someSessionData") return "OK"; return null; };
 
        var context = new System.Web.Fakes.ShimHttpContext();
        System.Web.Fakes.ShimHttpContext.CurrentGet = () => { return context; };
        System.Web.Fakes.ShimHttpContext.AllInstances.SessionGet =
            (o) =>
            {
                return session;
            };
        
        var result = instanceToTest.SomeMethod();
        Assert.IsTrue(result);
    }
}

Nous allons avoir besoin de Shims donc nous commençons par créer un ShimsContext. Dans le scope de ce contexte, nous pourrons intercepter les références à certaines classes pour les remplacer à la volée par nos objets fakes. On instancie ensuite un objet de type System.Web.SessionState.Fakes.ShimHttpSessionState qui est une copie de l’objet System.Web.SessionState sur laquelle nous avons tous les accès pour redéfinir les méthodes nécessaires. La ligne suivante utilise cette possibilité pour redéfinir la méthode get_Item(string) utilisée par l’indexeur de l’objet de session. Le framework de fakes expose par convention la propriété ItemGetString = nomdelapropriété + Get ou Set + type des arguments si des overloads existent. La propriété est de type Func, ce qui nous permet de fournir un nouveau comportement via une lambda. Ici, on retournera la valeur « OK » si la clé demandée est « someSessionData » et null dans les autres cas.

Dans la suite du test, nous créons de la même manière une instance de ShimHttpContext et nous redéfinissons la propriété statique Current pour qu’elle renvoie cette instance. Et toujours de la même manière, nous redéfinissons la propriété Session de toutes les instances de HttpContext pour qu’elles renvoient l’instance de ShimHttpSessionState créée juste avant.

Il suffit ensuite tout simplement d’appeler la méthode et de tester son retour. A l’exécution du test, les instances de HttpContext et de HttpSessionState sont remplacées à la volée par nos Shims et ce sont donc les implémentations de méthodes et de propriété des Shims qui sont appelées: Le test passe avec succès

Sur du code non prévu pour être testé, les Shims nous apportent donc des possibilités intéressantes. Les Stubs, même si ils sont moins intéressants permettent un gros gain de temps. Nous allons tenter de tester cette méthode simple:

public class SomeClass
{
    public bool SomeMethodWithCustomInterface(ISomeInterface myInterface)
    {
        var result = myInterface.SomeInterfaceOperation();
        return result;
    }
}

L’interface ISomeInterface est la suivante:

public interface ISomeInterface
{
    bool SomeInterfaceOperation();
}

Nous allons simplement générer l’assembly de fakes de la même manière sur l’assembly qui contient l’interface. On remarque que pour l’interface, un Stub existe dans l’assembly de fakes: StubISomeInterface. Nous allons simplement pouvoir l’instancier et redéfinir la méthode qui nous intéresse pour passer le fake à notre méthode à tester:

[TestMethod]
public void SomeTestMethodForCustomInterface()
{
    var instanceToTest = new SomeClass();
 
    var testInterfaceImplementation = new MsFakesExample.Fakes.StubISomeInterface();
    testInterfaceImplementation.SomeInterfaceOperation = () => { return true; };
 
    var result = instanceToTest.SomeMethodWithCustomInterface(testInterfaceImplementation);
    Assert.IsTrue(result);
}

Le test passe là encore.

Pour être un peu plus complet sur cet article, il faut savoir que d’autres frameworks existent pour faire ce genre de choses depuis pas mal de temps mais l’avantage de MS Fakes est d’être nativement disponible dans Visual Studio 2012 et d’être intégré au framework de tests de Microsoft.

Le code est disponible ICI

Personne n’a donc plus d’excuse pour ne pas faire ses tests unitaires! (petit message pour mon équipe qui se reconnaîtra🙂 )

Enjoy!

  1. Riadh
    27/03/2013 à 12:25

    Merci pour l’astuce, y’avait typemock mais qui est assez cher.

  2. 07/11/2013 à 6:37

    Merci Christophe pour ce retour.

    J’ai moi-même essayé les fakes récemment mais il y a plusieurs problèmes (à mon avis):
    – les assemblys fakes sont souvent recompilées et du coup, le temps total de compilation d’une solution se rallonge (ça se sent un peu),
    – les fakes ne sont supportés que sur la version ultimate je crois. J’ai récemment travaillé chez un client dont les devs avec VS Pro (et moi ultimate). J’ai donc du migrer les fakes vers un framework open-source (j’ai essayé FakeItEasy, c’est sympa).

    Le principe en tout cas c’est clairement une avancée pour faciliter le test.

    • 10/11/2013 à 7:37

      Les temps de compilation sont en effet très longs pour les grosses assemblies. La solution est de ne générer les fakes que sur les types dont on a besoin via le fichier de configuration. La doc est ici: http://msdn.microsoft.com/en-us/library/hh708916.aspx
      Pour l’ultimate en effet c’est dommage. Mais je suis toujours en ultimate :p

      • 12/11/2013 à 12:56

        Merci Christophe pour ton retour d’expérience.
        Effectivement, il s’agissait d’une « grosse » assembly qui contenait des types générés (à partir d’une base de données).

        Merci pour le lien, je vais checker ça.

  1. No trackbacks yet.

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s

%d blogueurs aiment cette page :