Utiliser des shims pour isoler votre application des autres assemblys pour des tests unitairesUse shims to isolate your application from other assemblies for unit testing

Les types shim sont l’une des deux technologies utilisées par le framework Microsoft Fakes pour vous permettre d’isoler facilement des composants testés de l’environnement.Shim types are one of two technologies that the Microsoft Fakes Framework uses to let you easily isolate components under test from the environment. Les shims détournent les appels à des méthodes spécifiques vers le code que vous écrivez dans le cadre de votre test.Shims divert calls to specific methods to code that you write as part of your test. De nombreuses méthodes retournent des résultats différents selon les conditions externes, mais un shim est sous le contrôle de votre test et peut retourner des résultats cohérents lors de chaque appel.Many methods return different results dependent on external conditions, but a shim is under the control of your test and can return consistent results at every call. Cela facilite l'écriture de vos tests.This makes your tests much easier to write.

Utilisez les shims pour isoler le code des assemblys qui ne font pas partie de votre solution.Use shims to isolate your code from assemblies that are not part of your solution. Pour isoler les composants de votre solution les uns des autres, nous vous recommandons d'utiliser les stubs.To isolate components of your solution from each other, we recommend that you use stubs.

Pour obtenir une vue d’ensemble et un guide de démarrage rapide, consultez Isolation du code sous test avec Microsoft Fakes.For an overview and quick start guidance, see Isolating Code Under Test with Microsoft Fakes

SpécificationsRequirements

  • Visual Studio EnterpriseVisual Studio Enterprise
  • Un projet .NET FrameworkA .NET Framework project

Note

Les projets .NET Standard ne sont pas pris en charge..NET Standard projects are not supported.

Exemple : Le bogue de l'an 2000Example: The Y2K bug

Prenons l’exemple d’une méthode qui lève une exception le 1er janvier 2000 :Let's consider a method that throws an exception on January 1st of 2000:

// code under test
public static class Y2KChecker {
    public static void Check() {
        if (DateTime.Now == new DateTime(2000, 1, 1))
            throw new ApplicationException("y2kbug!");
    }
}

Le test de cette méthode est problématique, car le programme dépend de DateTime.Now, une méthode qui dépend de l’horloge de l’ordinateur, une méthode dépendante de l’environnement et non déterministe.Testing this method is problematic because the program depends on DateTime.Now, a method that depends on the computer's clock, an environment-dependent, non-deterministic method. En outre, DateTime.Now étant une propriété statique, un type stub ne peut pas être utilisé ici.Furthermore, the DateTime.Now is a static property so a stub type can't be used here. Ce problème est symptomatique du problème d’isolation dans les tests unitaires : les programmes qui appellent directement les API de base de données communiquent avec les services web et il est donc difficile de procéder à des tests unitaires, car leur logique dépend de l’environnement.This problem is symptomatic of the isolation issue in unit testing: programs that directly call into database APIs, communicate with web services, and so on, are hard to unit test because their logic depends on the environment.

C'est dans ce cas que les types shim doivent être utilisés.This is where shim types should be used. Les types shim fournissent un mécanisme pour détourner les méthodes .NET vers un délégué défini par l’utilisateur.Shim types provide a mechanism to detour any .NET method to a user-defined delegate. Les types shim sont générés par le code par le générateur Fakes et utilisent des délégués, que nous appelons types shim, pour spécifier les nouvelles implémentations des méthodes.Shim types are code-generated by the Fakes generator, and they use delegates, which we call shim types, to specify the new method implementations.

Le test suivant montre comment utiliser le type shim, ShimDateTime, pour fournir une implémentation personnalisée de DateTime.Now :The following test shows how to use the shim type, ShimDateTime, to provide a custom implementation of DateTime.Now:

//unit test code
// create a ShimsContext cleans up shims
using (ShimsContext.Create()
    // hook delegate to the shim method to redirect DateTime.Now
    // to return January 1st of 2000
    ShimDateTime.NowGet = () => new DateTime(2000, 1, 1);
    Y2KChecker.Check();
}

Procédure d’utilisation des shimsHow to use Shims

Ajouter les assemblys FakesAdd Fakes Assemblies

  1. Dans l’Explorateur de solutions, développez les Références de votre projet de test unitaire.In Solution Explorer, expand your unit test project's References.

    • Si vous utilisez Visual Basic, vous devez sélectionner Afficher tous les fichiers dans la barre d’outils de l’Explorateur de solutions pour afficher la liste de références.If you are working in Visual Basic, you must select Show All Files in the Solution Explorer toolbar, in order to see the References list.
  2. Sélectionnez l'assembly contenant les définitions de classes pour lesquelles vous souhaitez créer des shims.Select the assembly that contains the classes definitions for which you want to create shims. Par exemple, si vous souhaitez effectuer un shim pour DateTime, sélectionnez System.dllFor example, if you want to shim DateTime, select System.dll

  3. Dans le menu contextuel, choisissez Ajouter un assembly Fakes.On the shortcut menu, choose Add Fakes Assembly.

Utiliser ShimsContextUse ShimsContext

Lors de l'utilisation de types shim dans un framework de tests unitaires, vous devez encapsuler le code de test dans un ShimsContext pour contrôler la durée de vie de vos shims.When using shim types in a unit test framework, you must wrap the test code in a ShimsContext to control the lifetime of your shims. Sans cette exigence, les shims dureraient jusqu’à l’arrêt d’AppDomain.If we didn't require this, your shims would last until the AppDomain shut down. La façon la plus facile de créer un ShimsContext consiste à utiliser la méthode statique Create() comme illustré dans le code suivant : The easiest way to create a ShimsContext is by using the static Create() method as shown in the following code:

//unit test code
[Test]
public void Y2kCheckerTest() {
  using(ShimsContext.Create()) {
    ...
  } // clear all shims
}

Il est essentiel de supprimer correctement chaque contexte de shim.It is critical to properly dispose each shim context. En règle générale, appelez toujours ShimsContext.Create à l'intérieur d'une instruction using pour garantir le nettoyage correct des shims inscrits.As a rule of thumb, always call the ShimsContext.Create inside of a using statement to ensure proper clearing of the registered shims. Par exemple, vous pouvez inscrire un shim pour une méthode de test qui remplace la méthode DateTime.Now par un délégué qui retourne toujours le premier janvier 2000.For example, you might register a shim for a test method that replaces the DateTime.Now method with a delegate that always returns the first of January 2000. Si vous oubliez d'effacer le shim inscrit dans la méthode de test, le reste de la série de tests retourne toujours le premier janvier 2000 comme valeur DateTime.Now.If you forget to clear the registered shim in the test method, the rest of the test run would always return the first of January 2000 as the DateTime.Now value. Cela peut être surprenant et déroutant.This might be surprising and confusing.

Écrire un test avec les shimsWrite a test with shims

Dans votre code de test, insérez un détour pour la méthode que vous souhaitez falsifier.In your test code, insert a detour for the method you want to fake. Exemple :For example:

[TestClass]
public class TestClass1
{
        [TestMethod]
        public void TestCurrentYear()
        {
            int fixedYear = 2000;

            using (ShimsContext.Create())
            {
              // Arrange:
                // Detour DateTime.Now to return a fixed date:
                System.Fakes.ShimDateTime.NowGet =
                () =>
                { return new DateTime(fixedYear, 1, 1); };

                // Instantiate the component under test:
                var componentUnderTest = new MyComponent();

              // Act:
                int year = componentUnderTest.GetTheCurrentYear();

              // Assert:
                // This will always be true if the component is working:
                Assert.AreEqual(fixedYear, year);
            }
        }
}
<TestClass()> _
Public Class TestClass1
    <TestMethod()> _
    Public Sub TestCurrentYear()
        Using s = Microsoft.QualityTools.Testing.Fakes.ShimsContext.Create()
            Dim fixedYear As Integer = 2000
            ' Arrange:
            ' Detour DateTime.Now to return a fixed date:
            System.Fakes.ShimDateTime.NowGet = _
                Function() As DateTime
                    Return New DateTime(fixedYear, 1, 1)
                End Function

            ' Instantiate the component under test:
            Dim componentUnderTest = New MyComponent()
            ' Act:
            Dim year As Integer = componentUnderTest.GetTheCurrentYear
            ' Assert:
            ' This will always be true if the component is working:
            Assert.AreEqual(fixedYear, year)
        End Using
    End Sub
End Class

Les noms de classe de shim sont obtenus en ajoutant le préfixe Fakes.Shim au nom de type d'origine.Shim class names are made up by prefixing Fakes.Shim to the original type name.

Les shims fonctionnent en insérant des détours dans le code de l’application testée.Shims work by inserting detours into the code of the application under test. Lors de chaque appel à la méthode d'origine, le système Fakes effectue un détour : au lieu d'appeler la véritable méthode, le code de votre shim est appelé.Wherever a call to the original method occurs, the Fakes system performs a detour, so that instead of calling the real method, your shim code is called.

Notez que les détours sont créés et supprimés au moment de l'exécution.Notice that detours are created and deleted at run time. Vous devez toujours créer un détour dans la durée de vie de ShimsContext.You must always create a detour within the life of a ShimsContext. Lorsqu'il est supprimé, tous les shims créés quand il était actif sont supprimés.When it is disposed, any shims you created while it was active are removed. La meilleure façon de procéder est à l'intérieur d'une instruction using.The best way to do this is inside a using statement.

Vous pouvez voir une erreur de génération indiquant que l'espace de noms Fakes n'existe pas.You might see a build error stating that the Fakes namespace does not exist. Cette erreur apparaît parfois lorsqu'il existe d'autres erreurs de compilation.This error sometimes appears when there are other compilation errors. Corrigez les autres erreurs et elle disparaîtra.Fix the other errors and it will vanish.

Shims pour différents types de méthodesShims for different kinds of methods

Les types shim vous permettent de remplacer toute méthode .NET, y compris les méthodes statiques ou méthodes non virtuelles, par vos propres délégués.Shim types allow you to replace any .NET method, including static methods or non-virtual methods, with your own delegates.

Méthodes statiquesStatic methods

Les propriétés permettant d'attacher des shims à des méthodes statiques sont placées dans un type shim.The properties to attach shims to static methods are placed in a shim type. Chaque propriété possède uniquement un accesseur Set qui peut être utilisé pour attacher un délégué à la méthode cible.Each property has only a setter that can be used to attach a delegate to the target method. Par exemple, prenons une classe MyClass avec une méthode statique MyMethod :For example, given a class MyClass with a static method MyMethod:

//code under test
public static class MyClass {
    public static int MyMethod() {
        ...
    }
}

Nous pouvons attacher un shim à MyMethod qui retourne toujours 5 :We can attach a shim to MyMethod that always returns 5:

// unit test code
ShimMyClass.MyMethod = () =>5;

Méthodes d’instance (pour toutes les instances)Instance methods (for all instances)

Comme les méthodes statiques, les méthodes d'instance peuvent faire l'objet d'un shim pour toutes les instances.Similarly to static methods, instance methods can be shimmed for all instances. Les propriétés permettant d'attacher ces shims sont placées dans un type imbriqué nommé AllInstances pour éviter toute confusion.The properties to attach those shims are placed in a nested type named AllInstances to avoid confusion. Par exemple, prenons une classe MyClass avec une méthode d'instance MyMethod :For example, given a class MyClass with an instance method MyMethod:

// code under test
public class MyClass {
    public int MyMethod() {
        ...
    }
}

Vous pouvez attacher un shim à MyMethod qui retourne toujours 5, quelle que soit l'instance :You can attach a shim to MyMethod that always returns 5, regardless of the instance:

// unit test code
ShimMyClass.AllInstances.MyMethod = () => 5;

La structure du type généré de ShimMyClass ressemble au code suivant :The generated type structure of ShimMyClass looks like the following code:

// Fakes generated code
public class ShimMyClass : ShimBase<MyClass> {
    public static class AllInstances {
        public static Func<MyClass, int>MyMethod {
            set {
                ...
            }
        }
    }
}

Notez que Fakes passe l’instance de runtime comme premier argument du délégué dans ce cas.Notice that Fakes passes the runtime instance as the first argument of the delegate in this case.

Méthodes d’instance (pour une instance de runtime)Instance methods (for one runtime instance)

Les méthodes d'instance peuvent également faire l'objet d'un shim par des délégués différents, selon le destinataire de l'appel.Instance methods can also be shimmed by different delegates, based on the receiver of the call. Cela permet à la même méthode d'instance d'avoir des comportements différents par instance du type.This enables the same instance method to have different behaviors per instance of the type. Les propriétés permettant de configurer ces shims sont des méthodes d'instance du type shim lui-même.The properties to set up those shims are instance methods of the shim type itself. Chaque type shim instancié est également associé à une instance brute d'un type ayant fait l'objet d'un shim.Each instantiated shim type is also associated with a raw instance of a shimmed type.

Par exemple, prenons une classe MyClass avec une méthode d'instance MyMethod :For example, given a class MyClass with an instance method MyMethod:

// code under test
public class MyClass {
    public int MyMethod() {
        ...
    }
}

Nous pouvons configurer deux types shim de MyMethod de façon à ce que le premier retourne toujours 5 et le deuxième retourne toujours 10 :We can set up two shim types of MyMethod such that the first one always returns 5 and the second always returns 10:

// unit test code
var myClass1 = new ShimMyClass()
{
    MyMethod = () => 5
};
var myClass2 = new ShimMyClass { MyMethod = () => 10 };

La structure du type généré de ShimMyClass ressemble au code suivant :The generated type structure of ShimMyClass looks like the following code:

// Fakes generated code
public class ShimMyClass : ShimBase<MyClass> {
    public Func<int> MyMethod {
        set {
            ...
        }
    }
    public MyClass Instance {
        get {
            ...
        }
    }
}

L'instance du type ayant fait l'objet d'un shim véritable est accessible via la propriété Instance :The actual shimmed type instance can be accessed through the Instance property:

// unit test code
var shim = new ShimMyClass();
var instance = shim.Instance;

Comme le type shim a également une conversion implicite vers le type ayant fait l'objet d'un shim, vous pouvez généralement utiliser tout simplement le type shim tel quel :The shim type also has an implicit conversion to the shimmed type, so you can usually simply use the shim type as is:

// unit test code
var shim = new ShimMyClass();
MyClass instance = shim; // implicit cast retrieves the runtime
                         // instance

ConstructeursConstructors

Les constructeurs peuvent également faire l'objet d'un shim pour attacher les types shim aux futurs objets.Constructors can also be shimmed in order to attach shim types to future objects. Chaque constructeur est exposé comme un constructeur de méthode statique dans le type shim.Each constructor is exposed as a static method Constructor in the shim type. Par exemple, prenons une classe MyClass avec un constructeur qui prend un entier :For example, given a class MyClass with a constructor taking an integer:

// code under test
public class MyClass {
    public MyClass(int value) {
        this.Value = value;
    }
    ...
}

Nous définissons le type shim du constructeur afin que chaque instance ultérieure retourne -5 quand l'accesseur Get Value est appelé, indépendamment de la valeur dans le constructeur :We set up the shim type of the constructor so that every future instance returns -5 when the Value getter is invoked, regardless of the value in the constructor:

// unit test code
ShimMyClass.ConstructorInt32 = (@this, value) => {
    var shim = new ShimMyClass(@this) {
        ValueGet = () => -5
    };
};

Chaque type shim expose deux constructeurs.Each shim type exposes two constructors. Le constructeur par défaut doit être utilisé quand une nouvelle instance est nécessaire, tandis que le constructeur prenant une instance ayant fait l'objet d'un shim comme argument doit être utilisé dans les shims constructeurs uniquement : The default constructor should be used when a fresh instance is needed, while the constructor taking a shimmed instance as argument should be used in constructor shims only:

// unit test code
public ShimMyClass() { }
public ShimMyClass(MyClass instance) : base(instance) { }

La structure du type généré de ShimMyClass ressemble au code suivant :The generated type structure of ShimMyClass resembles the following code:

// Fakes generated code
public class ShimMyClass : ShimBase<MyClass>
{
    public static Action<MyClass, int> ConstructorInt32 {
        set {
            ...
        }
    }

    public ShimMyClass() { }
    public ShimMyClass(MyClass instance) : base(instance) { }
    ...
}

Membres de baseBase members

Les propriétés du shim des membres de base sont accessibles en créant un shim pour le type de base et en passant l'instance enfant comme paramètre au constructeur de la classe de base du shim.The shim properties of base members can be accessed by creating a shim for the base type and passing the child instance as a parameter to the constructor of the base shim class.

Par exemple, prenons une classe MyBase avec une méthode d'instance MyMethod et un sous-type MyChild :For example, given a class MyBase with an instance method MyMethod and a subtype MyChild:

public abstract class MyBase {
    public int MyMethod() {
        ...
    }
}

public class MyChild : MyBase {
}

Nous pouvons configurer un shim de MyBase en créant un shim ShimMyBase :We can set up a shim of MyBase by creating a new ShimMyBase shim:

// unit test code
var child = new ShimMyChild();
new ShimMyBase(child) { MyMethod = () => 5 };

Notez que le type shim enfant est implicitement converti en instance enfant quand il est passé comme paramètre au constructeur de shim de base.Note that the child shim type is implicitly converted to the child instance when passed as a parameter to the base shim constructor.

La structure du type généré de ShimMyChild et ShimMyBase ressemble au code suivant :The generated type structure of ShimMyChild and ShimMyBase resembles the following code:

// Fakes generated code
public class ShimMyChild : ShimBase<MyChild> {
    public ShimMyChild() { }
    public ShimMyChild(Child child)
        : base(child) { }
}
public class ShimMyBase : ShimBase<MyBase> {
    public ShimMyBase(Base target) { }
    public Func<int> MyMethod
    { set { ... } }
}

Constructeurs statiquesStatic constructors

Les types shim exposent une méthode statique StaticConstructor pour effectuer un shim sur le constructeur statique d'un type.Shim types expose a static method StaticConstructor to shim the static constructor of a type. Étant donné que les constructeurs statiques sont exécutés une fois seulement, vous devez vous assurer que le shim est configuré avant d’accéder à n’importe quel membre du type.Since static constructors are executed once only, you need to make sure that the shim is configured before any member of the type is accessed.

FinaliseursFinalizers

Les finaliseurs ne sont pas pris en charge dans Fakes.Finalizers are not supported in Fakes.

Méthodes privéesPrivate methods

Le générateur de code Fakes crée des propriétés de shim pour les méthodes privées dont la signature comporte seulement des types visibles, autrement dit des types de paramètres et un type de retour visibles.The Fakes code generator creates shim properties for private methods that only have visible types in the signature, that is, parameter types and return type visible.

Interfaces de liaisonBinding interfaces

Quand un type ayant fait l'objet d'un shim implémente une interface, le générateur de code émet une méthode qui lui permet de lier tous les membres de cette interface à la fois.When a shimmed type implements an interface, the code generator emits a method that allows it to bind all the members from that interface at once.

Par exemple, prenons une classe MyClass qui implémente IEnumerable<int> :For example, given a class MyClass that implements IEnumerable<int>:

public class MyClass : IEnumerable<int> {
    public IEnumerator<int> GetEnumerator() {
        ...
    }
    ...
}

Nous pouvons effectuer un shim sur les implémentations d'IEnumerable<int> dans MyClass en appelant la méthode Bind :We can shim the implementations of IEnumerable<int> in MyClass by calling the Bind method:

// unit test code
var shimMyClass = new ShimMyClass();
shimMyClass.Bind(new List<int> { 1, 2, 3 });

La structure du type généré de ShimMyClass ressemble au code suivant :The generated type structure of ShimMyClass resembles the following code:

// Fakes generated code
public class ShimMyClass : ShimBase<MyClass> {
    public ShimMyClass Bind(IEnumerable<int> target) {
        ...
    }
}

Modification du comportement par défautChanging the default behavior

Chaque type shim généré contient une instance de l'interface IShimBehavior, via la propriété ShimBase<T>.InstanceBehavior.Each generated shim type holds an instance of the IShimBehavior interface, through the ShimBase<T>.InstanceBehavior property. Le comportement est utilisé chaque fois qu'un client appelle un membre d'instance qui n'a pas fait l'objet d'un shim de façon explicite.The behavior is used whenever a client calls an instance member that was not explicitly shimmed.

Si le comportement n’a pas été défini explicitement, il utilise l’instance retournée par la propriété statique ShimsBehaviors.Current.If the behavior has not been explicitly set, it uses the instance returned by the static ShimsBehaviors.Current property. Par défaut, cette propriété retourne un comportement qui lève une exception NotImplementedException.By default, this property returns a behavior that throws a NotImplementedException exception.

Ce comportement peut être modifié à tout moment en définissant la propriété InstanceBehavior sur toute instance de shim.This behavior can be changed at any time by setting the InstanceBehavior property on any shim instance. Par exemple, l'extrait de code suivant remplace le shim par un comportement qui ne fait rien ou retourne la valeur par défaut du type de retour, autrement dit default(T) :For example, the following snippet changes the shim to a behavior that does nothing or returns the default value of the return type—that is, default(T):

// unit test code
var shim = new ShimMyClass();
//return default(T) or do nothing
shim.InstanceBehavior = ShimsBehaviors.DefaultValue;

Le comportement peut également être modifié globalement pour toutes les instances ayant fait l'objet d'un shim pour lesquelles la propriété InstanceBehavior n'a pas été définie explicitement en définissant la propriété statique ShimsBehaviors.Current :The behavior can also be changed globally for all shimmed instances for which the InstanceBehavior property was not explicitly set by setting the static ShimsBehaviors.Current property:

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

Détection des accès à l’environnementDetecting environment accesses

Il est possible d'associer un comportement à tous les membres, y compris les méthodes statiques, d'un type particulier en assignant le comportement ShimsBehaviors.NotImplemented à la propriété statique Behavior du type shim correspondant :It is possible to attach a behavior to all the members, including static methods, of a particular type by assigning the ShimsBehaviors.NotImplemented behavior to the static property Behavior of the corresponding shim type:

// unit test code
// assigning the not implemented behavior
ShimMyClass.Behavior = ShimsBehaviors.NotImplemented;
// shorthand
ShimMyClass.BehaveAsNotImplemented();

Accès concurrentielConcurrency

Les types shim s’appliquent à tous les threads de l’AppDomain et n’ont pas d’affinité de thread.Shim types apply to all threads in the AppDomain and don't have thread affinity. Il s'agit d'un fait important si vous prévoyez d'utiliser un Test Runner qui prend en charge la concurrence : les tests impliquant des types shim ne peuvent pas s'exécuter simultanément.This is an important fact if you plan to use a test runner that support concurrency: tests involving shim types cannot run concurrently. Cette propriété n’est pas appliquée par le runtime Fakes.This property is not enforced by the Fakes runtime.

Appel de la méthode d’origine à partir de la méthode shimCalling the original method from the shim method

Supposons que nous voulons réellement écrire le texte vers le système de fichiers après avoir validé le nom du fichier passé à la méthode.Imagine that we wanted to actually write the text to the file system after validating the file name passed to the method. Dans ce cas, nous voulons appeler la méthode d'origine au milieu de la méthode shim.In that case, we would want to call the original method in the middle of the shim method.

La première approche pour résoudre ce problème consiste à encapsuler un appel à la méthode d'origine en utilisant un délégué et ShimsContext.ExecuteWithoutShims() comme dans le code suivant :The first approach to solve this problem is to wrap a call to the original method using a delegate and ShimsContext.ExecuteWithoutShims() as in the following code:

// unit test code
ShimFile.WriteAllTextStringString = (fileName, content) => {
  ShimsContext.ExecuteWithoutShims(() => {

      Console.WriteLine("enter");
      File.WriteAllText(fileName, content);
      Console.WriteLine("leave");
  });
};

Une autre approche consiste à affecter au shim la valeur null, à appeler la méthode d'origine et à restaurer le shim.Another approach is to set the shim to null, call the original method and restore the shim.

// unit test code
ShimsDelegates.Action<string, string> shim = null;
shim = (fileName, content) => {
  try {
    Console.WriteLine("enter");
    // remove shim in order to call original method
    ShimFile.WriteAllTextStringString = null;
    File.WriteAllText(fileName, content);
  }
  finally
  {
    // restore shim
    ShimFile.WriteAllTextStringString = shim;
    Console.WriteLine("leave");
  }
};
// initialize the shim
ShimFile.WriteAllTextStringString = shim;

LimitationsLimitations

Les shims ne peuvent pas être utilisés sur tous les types à partir de la bibliothèque de classes de base .NET mscorlib et System.Shims cannot be used on all types from the .NET base class library mscorlib and System.

Voir aussiSee also