Partager via


Utilisation de stubs pour isoler des parties de votre application les unes des autres pour des tests unitaires

Les types de stub sont l'une de deux technologies que l'infrastructure de faux Microsoft la fournit pour vous permettre de facilement isoler un composant que vous testez d'autres composants qu'elle appelle.Un stub est un petit morceau de code qui remplace un autre composant pendant le test.L'avantage d'utiliser un stub est qu'il retourne des résultats cohérents, rendant le test plus facile à écrire.Et vous pouvez exécuter des tests même si les autres composants ne fonctionnent pas encore.

Pour une présentation et un guide de démarrage rapide à la valeur false, consultez l' Isolation du code sous test avec Microsoft Fakes.

Pour utiliser des stubs, vous devez écrire votre composant afin qu'il utilise uniquement des interfaces, classes, pas pour faire référence à d'autres parties de l'application.C'est une bonne pratique en matière de conception parce qu'il effectue des modifications dans une proportion moins probables pour nécessiter des modifications des autres.Pour tester, elle vous permet de remplacer un stub pour un vrai composant.

Dans le diagramme, le StockAnalyzer composant est celui que nous souhaitons tester.Il utilise normalement un autre composant, RealStockFeed.Mais RealStockFeed retourne des résultats différents chaque fois que ses méthodes sont appelées, le rendant difficile à tester StockAnalyzer.Pendant le test, nous le remplaçons par une autre classe, StubStockFeed.

Classes Real and Stub conforment à une interface.

Étant donné que les stubs reposent sur votre pouvoir structurer votre code de cette façon, vous utilisez généralement des stubs pour isoler une partie de votre application des autres.Pour l'isolation d'autres assemblys qui ne sont pas sous votre contrôle, tel que System.dll, vous utiliserez normalement des cales.Consultez Utilisation de shims pour isoler votre application des autres assemblys pour des tests unitaires.

Configuration requise

  • Visual Studio Ultimate

Dans cette rubrique

Procédure stubs de utilisation

Hh549174.collapse_all(fr-fr,VS.110).gifConception de l'injection de dépendance

Pour utiliser des stubs, votre application doit être conçue afin que les différents composants ne dépendent pas l'un de l'autre, mais uniquement en fonction de les définitions d'interface.Au lieu de l'accouplement au moment de la compilation, les composants sont connectés au moment de l'exécution.Ce modèle permet d'effectuer le logiciel qui est fiable et facile à mettre à jour, parce que les modifications ont tendance à ne pas propager au delà de les limites de composants.Nous vous recommandons de le suivre même si vous n'utilisez pas les stubs.Si vous écrivez du nouveau code, il est facile de suivre injection de dépendance le modèle.Si vous écrivez des tests pour le logiciel existant, vous devrez peut-être le refactorisation.Si ce serait irréaliste, vous pouvez envisager d'utiliser des cales à la place.

Commençons cette discussion avec un exemple de motivation, celui du diagramme.La classe StockAnalyzer lit les exécution d'actions et génère des résultats intéressants.Elle a des méthodes publiques, que nous souhaitons tester.Pour contenir des éléments simples, tout regardons une de ces méthodes, très simple qui stocke le prix actuel d'un partage particulier.Nous souhaitons écrire un test unitaire de cette méthode.Voici le projet de test :

        [TestMethod]
        public void TestMethod1()
        {
            // Arrange:
            var analyzer = new StockAnalyzer();
            // Act:
            var result = analyzer.GetContosoPrice();
            // Assert:
            Assert.AreEqual(123, result); // Why 123?
        }
    <TestMethod()> Public Sub TestMethod1()
        ' Arrange:
        Dim analyzer = New StockAnalyzer()
        ' Act:
        Dim result = analyzer.GetContosoPrice()
        ' Assert:
        Assert.AreEqual(123, result) ' Why 123?
    End Sub

Un problème avec ce test est immédiatement évidente : les exécution d'actions varient, et donc l'assertion échoue habituellement.

Un autre problème peut être que le composant de StockFeed, qui est utilisé par le StockAnalyzer, est toujours en cours de développement.Voici le projet de code de la méthode en cours de test :

        public int GetContosoPrice()
        {
            var stockFeed = new StockFeed(); // NOT RECOMMENDED
            return stockFeed.GetSharePrice("COOO");
        }
    Public Function GetContosoPrice()
        Dim stockFeed = New StockFeed() ' NOT RECOMMENDED
        Return stockFeed.GetSharePrice("COOO")
    End Function

Sans modification, cette méthode ne peut pas être compilée ou peut lever une exception car le travail dans la classe de StockFeed n'est pas encore terminé.

L'injection d'interface traite les deux problèmes.

L'injection d'interface applique la règle suivante :

  • Le code de n'importe quel composant de votre application doit jamais explicitement référence à une classe dans un autre composant, dans une déclaration ou dans une instruction d' new .À la place, les variables et les paramètres doivent être déclarés avec des interfaces.Les instances du composant doivent être créées uniquement par le conteneur du composant.

    Par « composant » dans ce cas nous voulons indiquer une classe, ou un groupe de classes que vous développez et mettre à jour ensemble.En général, un composant est le code dans un projet Visual Studio.Il est moins important de découpler des classes dans l'un composant, car ils sont mis à jour en même temps.

    Il n'est pas aussi également important de découpler vos composants d'une plateforme relativement stable telles que System.dll.Les interfaces d'écriture pour toutes ces classes encombreraient votre code.

Le code de StockAnalyzer peut être améliorée en découplant du StockFeed à l'aide d'une interface comme suit :

    public interface IStockFeed
    {
        int GetSharePrice(string company);
    }

    public class StockAnalyzer
    {
        private IStockFeed stockFeed;
        public Analyzer(IStockFeed feed)
        {
            stockFeed = feed;
        }
        public int GetContosoPrice()
        {
            return stockFeed.GetSharePrice("COOO");
        }
    }
Public Interface IStockFeed
    Function GetSharePrice(company As String) As Integer
End Interface

Public Class StockAnalyzer
    ' StockAnalyzer can be connected to any IStockFeed:
    Private stockFeed As IStockFeed
    Public Sub New(feed As IStockFeed)
        stockFeed = feed
    End Sub  
    Public Function GetContosoPrice()
        Return stockFeed.GetSharePrice("COOO")
    End Function
End Class

Dans cet exemple, StockAnalyzer est passé une implémentation d'un IStockFeed lorsqu'il est construit.Dans l'application complète, le code d'initialisation exécuterait la connexion :

analyzer = new StockAnalyzer(new StockFeed())

Il existe de manière plus flexibles d'effectuer cette connexion.Par exemple, StockAnalyzer peut recevoir un objet de fabrique qui peut instancier des implémentations d'IStockFeed dans différentes conditions.

Hh549174.collapse_all(fr-fr,VS.110).gifGénère des stubs

Vous avez découplé la classe à tester les autres composants qu'il utilise.En plus de exécuter l'application plus fiable et flexible, le découplage vous permet de connecter le composant testée pour les implémentations du stub des interfaces pour le test.

Vous pouvez simplement écrire des stubs comme des classes comme d'habitude.Mais les false Microsoft offre un moyen plus dynamique de créer le plus approprié stub pour chaque test.

Pour utiliser des stubs, vous devez d'abord créer des types de stub des définitions d'interface.

Ajouter un assembly de faux

  1. Dans l'explorateur de solutions, développez Référencesde votre projet de test unitaire.

    • Si vous travaillez en Visual Basic, vous devez sélectionner Afficher tous les fichiers dans la barre d'outils de l'explorateur de solutions, afin de consulter la liste de références.
  2. Sélectionnez l'assembly qui contient des définitions d'interface pour lesquelles vous souhaitez créer des stubs.

  3. Dans le menu contextuel, choisissez Ajouter un assembly Fakes.

Hh549174.collapse_all(fr-fr,VS.110).gifÉcrivez votre test avec des stubs

[TestClass]
class TestStockAnalyzer
{
    [TestMethod]
    public void TestContosoStockPrice()
    {
      // Arrange:

        // Create the fake stockFeed:
        IStockFeed stockFeed = 
             new StockAnalysis.Fakes.StubIStockFeed() // Generated by Fakes.
                 {
                     // Define each method:
                     // Name is original name + parameter types:
                     GetSharePriceString = (company) => { return 1234; }
                 };

        // In the completed application, stockFeed would be a real one:
        var componentUnderTest = new StockAnalyzer(stockFeed);

      // Act:
        int actualValue = componentUnderTest.GetContosoPrice();

      // Assert:
        Assert.AreEqual(1234, actualValue);
    }
    ...
}
<TestClass()> _
Class TestStockAnalyzer

    <TestMethod()> _
    Public Sub TestContosoStockPrice()
        ' Arrange:
        ' Create the fake stockFeed:
        Dim stockFeed As New StockAnalysis.Fakes.StubIStockFeed
        With stockFeed
            .GetSharePriceString = Function(company)
                                       Return 1234
                                   End Function
        End With
        ' In the completed application, stockFeed would be a real one:
        Dim componentUnderTest As New StockAnalyzer(stockFeed)
        ' Act:
        Dim actualValue As Integer = componentUnderTest.GetContosoPrice
        ' Assert:
        Assert.AreEqual(1234, actualValue)
    End Sub
End Class

La partie particulière de magie ici est la classe StubIStockFeed.Pour chaque public dans l'assembly référencé, des faux Microsoft que le mécanisme génère une classe stub.Le nom de la classe stub est type dérivé noms du nom de l'interface, par « Fakes.Stub » comme préfixe, et de paramètre ajouté.

Des stubs sont également générés pour les accesseurs set et accesseurs Set des propriétés, des événements, et des méthodes génériques.

Hh549174.collapse_all(fr-fr,VS.110).gifVérifier les valeurs de paramètre

Vous pouvez vérifier que lorsque votre composant effectue un appel à un autre composant, il passe les valeurs correctes.Vous pouvez définir ou une assertion dans le stub, ou vous pouvez stocker la valeur et vérifier au corps principal du test.Par exemple :

[TestClass]
class TestMyComponent
{
       
    [TestMethod]
    public void TestVariableContosoPrice()
    {
     // Arrange:
        int priceToReturn;
        string companyCodeUsed;
        var componentUnderTest = new StockAnalyzer(new StubIStockFeed()
            {
               GetSharePriceString = (company) => 
                  { 
                     // Store the parameter value:
                     companyCodeUsed = company;
                     // Return the value prescribed by this test:
                     return priceToReturn;
                  };
            };
        // Set the value that will be returned by the stub:
        priceToReturn = 345;

     // Act:
        int actualResult = componentUnderTest.GetContosoPrice(priceToReturn);

     // Assert:
        // Verify the correct result in the usual way:
        Assert.AreEqual(priceToReturn, actualResult);

        // Verify that the component made the correct call:
        Assert.AreEqual("COOO", companyCodeUsed);
    }
...}
<TestClass()> _
Class TestMyComponent
    <TestMethod()> _
    Public Sub TestVariableContosoPrice()
        ' Arrange:
        Dim priceToReturn As Integer
        Dim companyCodeUsed As String = ""
        Dim stockFeed As New StockAnalysis.Fakes.StubIStockFeed()
        With stockFeed
            ' Implement the interface's method:
            .GetSharePriceString = _
                Function(company)
                    ' Store the parameter value:
                    companyCodeUsed = company
                    ' Return a fixed result:
                    Return priceToReturn
                End Function
        End With
        ' Create an object to test:
        Dim componentUnderTest As New StockAnalyzer(stockFeed)
        ' Set the value that will be returned by the stub:
        priceToReturn = 345

        ' Act:
        Dim actualResult As Integer = componentUnderTest.GetContosoPrice()

        ' Assert:
        ' Verify the correct result in the usual way:
        Assert.AreEqual(priceToReturn, actualResult)
        ' Verify that the component made the correct call:
        Assert.AreEqual("COOO", companyCodeUsed)
    End Sub
...
End Class

Stubs pour différents types de membres de type

Hh549174.collapse_all(fr-fr,VS.110).gifMéthodes

Comme indiqué dans l'exemple, les méthodes peuvent être déracinées en liant un délégué à une instance de la classe stub.Le nom du type de stub est dérivé des noms de la méthode et des paramètres.Par exemple, vu l'interface et la méthode suivante MyMethod et IMyInterface :

// application under test
interface IMyInterface 
{
    int MyMethod(string value);
}

Nous joignons un stub à MyMethod qui retourne toujours 1 :

// unit test code
  var stub = new StubIMyInterface ();
  stub.MyMethodString = (value) => 1;

Si vous ne fournissez pas un stub pour une fonction, des faux génèrent une fonction qui retourne la valeur par défaut du type de retour.Pour les nombres, la valeur par défaut est 0, et pour les types de classe c'est null (c) ou Nothing (Visual Basic).

Hh549174.collapse_all(fr-fr,VS.110).gifPropriétés

Les accesseurs de propriétés Get et Set sont exposés comme des délégués distincts et peuvent être stubbés séparément.Par exemple, considérez la propriété Value de IMyInterface:

// code under test
interface IMyInterface 
{
    int Value { get; set; }
}

Nous joignons des délégués aux accesseurs Get et Set de Value pour simuler une propriété automatique :

// unit test code
int i = 5;
var stub = new StubIMyInterface();
stub.ValueGet = () => i;
stub.ValueSet = (value) => i = value;

Si vous ne fournissez pas de méthodes stub pour l'accesseur Set ou l'accesseur Get de propriété, les false génèrent un stub qui stocke des valeurs, afin que la propriété du stub fonctionne comme une variable simple.

Hh549174.collapse_all(fr-fr,VS.110).gifÉvénements

Les événements sont exposés en tant que champs de délégué.Par conséquent, tout événement stubbé peut être déclenché simplement en appelant le champ de stockage d'événements.Prenons l'interface suivante pour stubber :

// code under test
interface IWithEvents 
{
    event EventHandler Changed;
}

Pour déclencher l'événement Changed , nous appelons simplement le délégué de charge :

// unit test code
  var withEvents = new StubIWithEvents();
  // raising Changed
  withEvents.ChangedEvent(withEvents, EventArgs.Empty);

Hh549174.collapse_all(fr-fr,VS.110).gifMéthodes génériques

Il est possible de stubber des méthodes génériques en fournissant un délégué pour chaque instanciation souhaitée de la méthode.Par exemple, vu l'interface suivante qui contient une méthode générique :

// code under test
interface IGenericMethod 
{
    T GetValue<T>();
}

vous pouvez écrire un test qui déracine l'instanciation d' GetValue<int> :

// unit test code
[TestMethod]
public void TestGetValue() 
{
    var stub = new StubIGenericMethod();
    stub.GetValueOf1<int>(() => 5);

    IGenericMethod target = stub;
    Assert.AreEqual(5, target.GetValue<int>());
}

Si le code était d'appeler GetValue<T> avec toute autre instanciation, le stub appellerait simplement le comportement.

Hh549174.collapse_all(fr-fr,VS.110).gifStubs de classes virtuelles

Dans les exemples précédents, les stubs ont été générés des interfaces.Vous pouvez également générer des stubs d'une classe qui a virtuel ou des membres abstraits.Par exemple :

// Base class in application under test
    public abstract class MyClass
    {
        public abstract void DoAbstract(string x);
        public virtual int DoVirtual(int n)
        { return n + 42; }
        public int DoConcrete()
        { return 1; }
    }

Dans le stub généré de cette classe, vous pouvez définir des méthodes délégué pour DoAbstract() et DoVirtual(), mais pas DoConcrete().

// unit test
  var stub = new Fakes.MyClass();
  stub.DoAbstractString = (x) => { Assert.IsTrue(x>0); };
  stub.DoVirtualInt32 = (n) => 10 ;
  

Si vous ne fournissez pas de délégué d'une méthode virtuelle, des faux ou peuvent fournir le comportement par défaut, ou il peut appeler la méthode dans la classe de base.Pour faire appeler la méthode de base, affectez à la propriété d' CallBase :

// unit test code
var stub = new Fakes.MyClass();
stub.CallBase = false;
// No delegate set – default delegate:
Assert.AreEqual(0, stub.DoVirtual(1));

stub.CallBase = true;
//No delegate set - calls the base:
Assert.AreEqual(43,stub.DoVirtual(1));

Stubs de débogage

Les types de stub sont conçus pour fournir une expérience fluide de débogage.Par défaut, le débogueur est chargé d'effectuer un pas-à- pas principal de tout code généré, il doit passer directement dans les implémentations de membres personnalisés qui ont été attachées au stub.

Limitations du stub

  1. Les signatures de méthodes avec des pointeurs ne sont pas prises en charge.

  2. Les classes sealed ou les méthodes statiques ne peuvent pas être stubbés car les types stub s'appuient sur l'expédition virtuelle de méthode.Pour ces cas, utilisez shim comme décrit dans Utilisation de shims pour isoler votre application des autres assemblys pour des tests unitaires

Modifier le comportement par défaut des stubs

Chaque type de stub généré contient une instance de l'interface IStubBehavior (via la propriété IStub.InstanceBehavior ).Le comportement est appelé chaque fois qu'un client appelle un membre sans le délégué personnalisé attaché.Si le comportement n'a pas été défini, il utilise l'instance retournée par la propriété StubsBehaviors.Current .Par défaut, cette propriété retourne un comportement qui lève une exception NotImplementedException .

Le comportement peut être modifié à tout moment en affectant à la propriété InstanceBehavior sur toute instance du stub.Par exemple, l'extrait de code suivant remplace un comportement qui ne fait rien et retourne la valeur par défaut du type de retour : default(T):

// unit test code
var stub = new StubIFileSystem();
// return default(T) or do nothing
stub.InstanceBehavior = StubsBehaviors.DefaultValue;

Le comportement peut également être modifié globalement pour tous les objets du stub pour lesquels le comportement n'a pas été défini en affectant à la propriété StubsBehaviors.Current :

// unit test code
//change default behavior for all stub instances
//where the behavior has not been set
StubBehaviors.Current = 
    BehavedBehaviors.DefaultValue;

Ressources externes

Hh549174.collapse_all(fr-fr,VS.110).gifAide

Tester pour la Livraison Continue avec Visual Studio 2012 – Chapître 2 : Test Unitaire : Tester l'Intérieur

Voir aussi

Concepts

Isolation du code sous test avec Microsoft Fakes