Freigeben über


Verwenden von Shims, um zu Komponententests die Anwendung von anderen Assemblys zu trennen

Es wurden {0} Shim-Typen generiert. sind eine von zwei Technologien, die die Microsoft Fakes-Frameworkverwendung, Sie Komponenten getesteten von der Umgebung isoliert leicht zu lassen.Shims leiten Aufrufe von bestimmten Methoden, um zu codieren, die Sie als Teil des Tests schreiben.Viele Methoden geben die verschiedene Ergebnisse zurück, die aus externen Bedingungen abhängig sind, ein Shim ist unter Kontrolle des Tests kann und konsistente Ergebnisse bei jedem Aufruf zurückgeben.Damit werden die Tests einfacher zu schreiben.

Verwenden Sie Shims, um den Code von den Assemblys zu isolieren, die nicht Teil der Projektmappe sind.Um Komponenten der Projektmappe voneinander isolieren, wird empfohlen Stubs.

Für einen Übersichts- und Schnellstartprozessleitfaden finden Sie unter Isolieren von getestetem Code mithilfe von Microsoft Fakes

Voraussetzungen

  • Visual Studio Ultimate

Siehe Video (1h16): Testen des UN-testfähigen Code mit Fälschungen in Visual Studio 2012

In diesem Thema

Dies ist, was Sie in diesem Thema erfahren:

Beispiel: Der Y2K-Fehler

Wie Shims verwendet

  • Fakes-Assemblys hinzufügen

  • ShimsContext verwenden

  • Schreiben von Tests mit Shims

Shims für verschiedene Arten von Methoden

Ändern des Standardverhaltens

Erkennen von Umgebungszugriffen

Parallelität

Aufrufen der ursprünglichen Methode von der Shimmethode

Einschränkungen

Beispiel: Der Y2K-Fehler

Fügen wir eine Methode verwenden, die eine Ausnahme am 1. Januar von 2000 auslöst:

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

Diese Methode zu testen ist, da das Programm von DateTime.Now abhängig ist, eine Methode, die von der Uhr des Computers abhängig ist, eine umgebungsabhängige, nicht deterministische besonders problematisch.Darüber hinaus ist DateTime.Now eine statische Eigenschaft, sodass ein Stubtyp hier nicht verwendet werden.Dieses Problem ist vom Isolationsproblem im Komponententest kann: Programme, die direkt in Datenbank aufrufen, APIs sind Webdienste, sind u. schwer in den Komponententest, da ihre Logik von der Umgebung.

Dies ist, wo Shimtypen verwendet werden sollten.Shimtypen stellen einen Mechanismus, um jede .NET-Methode zu einem benutzerdefinierten Delegaten umzuleiten.Shimtypen werden durch den Fälschungsgenerator Code-generiert, und verwenden diese Delegaten, die von Shimtypen aufrufen, um den neuen Methodenimplementierungen anzugeben.

Der folgende Test wird gezeigt, wie der Shimtyp, ShimDateTime verwendet, um eine benutzerdefinierte Implementierung von DateTime.Now bereitzustellen:

//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();
}

Verwendung von Shims

Fakes-Assemblys hinzufügen

  1. Erweitern Sie in Projektmappen-Explorer, Verweise des Datenbankkomponententest-Projekts.

    • Wenn Sie mit Visual Basic arbeiten, müssen Sie in der Symbolleiste des Projektmappen-Explorers Alle Dateien anzeigen auswählen, um die Verweisliste zu finden.
  2. Wählen Sie die Assembly aus, die die Klassendefinitionen enthält, für die Sie Shims erstellen möchten.Wenn Sie z DateTime versetzen möchten, wählen Sie die System.dll

  3. Klicken Sie im Kontextmenü auf Fakes-Assembly hinzufügen Sie aus.

ShimsContext verwenden

Wenn, Shim verwenden, Typen eines Komponententestframework, müssen Sie den Code in ShimsContext umschließen, um die Lebensdauer der Shims zu steuern ein.Wenn wir dieses nicht benötigen, würden die Shims dauern, bis die AppDomain heruntergefahren wird.Die einfachste Art, ShimsContext zu erstellen ist, indem die statische Create()-Methode wie im folgenden Code gezeigt - Methode:

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

Es ist wichtig, die Shimkontext ordnungsgemäß freizugeben.Als Faustregel rufen Sie immer ShimsContext.Create innerhalb einer using-Anweisung auf, um richtiges Löschen der registrierten Shims sicherzustellen.Beispielsweise registrierten Sie möglicherweise einen Shim für eine Testmethode, die die DateTime.Now-Methode durch einen Delegaten ersetzen, der immer das erste vom Januar 2000 zurückgibt.Wenn Sie vergessen, registrierten Shim in der Testmethode zu löschen, wird der Rest des Testlaufs immer der erste vom Januar 2000 da der DateTime.Now-Wert zurückgeben.Dies könnte überraschend und verwirrend.

Einen Test mit Shims schreiben

In dem Testcode fügen Sie einen für indirekte Art die Methode ein, die Sie fälschen möchten.Beispiel:

[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

Shimklassennamen werden gebildet, indem dem ursprünglichen Typnamen Fakes.Shim vorangestellt wird.

Shims arbeiten, mit Umleitungen in den getesteten Anwendung einfügen.Unabhängig wo tritt ein Aufruf der ursprünglichen Methode, die Fälschungen auf, der System einen indirekte Art ausführt, sodass, anstatt, die echte Methode aufzurufen aufgerufen wird, das Shimcode.

Beachten Sie, dass Umleitungen zur Laufzeit erstellt und gelöscht werden.Sie müssen einen indirekte Art innerhalb der Lebensdauer von ShimsContext immer erstellen.Wenn sie freigegeben, alle Shims, die Sie erstellt haben, während diese entfernt wird aktiv war.Die beste Methode, hierzu ist innerhalb einer using-Anweisung.

Sie könnten ein Buildfehler ausgegeben wird, dass der Fälschungsnamespace nicht vorhanden ist.Dieser Fehler tritt auf, wenn sie andere Kompilierungsfehler gibt.Beheben Sie die anderen Fehler und er wird.

Shims für verschiedene Arten von Methoden

Shimtypen ermöglichen, können Sie jede .NET-Methode, einschließlich Methoden oder nicht virtuelle Methoden, durch eigene Delegaten zu ersetzen.

Statische Methoden

Die Eigenschaften, von Shims zu statischen Methoden anzufügen werden in einen Shimtyp eingefügt.Jede Eigenschaft verfügt über nur einen Setter, der verwendet werden kann, um einen Delegaten für die Zielmethode anzufügen.Beispiel einer MyClass-Klasse mit einer statischen Methode MyMethod angegeben:

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

Wir können einen Shim zu MyMethod anfügen, der immer 5 zurückgibt:

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

Instanzmethoden (für alle Instanzen)

Entsprechend zu statischen Methoden, können alle Instanzmethoden für Instanzen ausgeglichen werden.Die Eigenschaften, um diese Shims anzufügen werden in einen geschachtelten Typ gespeichert, der AllInstances, um Verwechslungen auszuschließen genannt wird.Beispiel einer MyClass-Klasse mit einer Instanzmethode MyMethod angegeben:

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

Sie können einen Shim zu MyMethod, der immer 5 zurückgibt, unabhängig von der Instanz an:

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

Die generierte Typstruktur von ShimMyClass-Aussehung wie dem folgenden Code:

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

Beachten Sie, dass Fälschungen die Laufzeitinstanz als Erstes Argument des Delegaten in diesem Fall führt.

Instanzmethoden (für eine Laufzeitinstanz)

Instanzmethoden können von anderen Delegaten, abhängig vom Empfänger des Aufrufs auch ausgeglichen werden.Dadurch kann dieselbe Instanzmethode, um unterschiedliche Verhalten pro Instanz des Typs enthalten.Die Eigenschaften, um diese Shims zu installieren sind Instanzmethoden des Shimtyps selbst.Jeder instanziierte Shimtyp ist in eine unformatierte Instanz eines ausgeglichenen Typs zugeordnet.

Beispiel einer MyClass-Klasse mit einer Instanzmethode MyMethod angegeben:

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

Wir können zwei Shimtypen MyMethod installieren, sodass das erste immer 5 zurückgibt und das zweite immer 10 zurückgibt:

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

Die generierte Typstruktur von ShimMyClass-Aussehung wie dem folgenden Code:

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

Auf die tatsächliche ausgeglichene Typinstanz kann von Instanzeigenschaft zugegriffen werden:

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

Der Shimtyp verfügt auch eine implizite Konvertierung zum ausgeglichenen Typ, sodass Sie den Shimtyp normalerweise einfach verwenden, z ist:

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

Konstruktoren

Konstruktoren können auch ausgeglichen werden, um Shimtypen auf zukünftige Objekten anzufügen.Jeder Konstruktor wird als Konstruktor der statischen Methode im Shimtyp verfügbar gemacht.Beispielsweise einer Klasse MyClass mit einem Konstruktor zugewiesen, der eine ganze Zahl akzeptiert:

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

Wir installieren den Shimtyp des Konstruktors, damit jede Instanz zukünftige -5, wenn der Wertgetter aufgerufen wird, unabhängig vom Wert im Konstruktor zurückgibt:

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

Beachten Sie, dass jeder Shimtyp zwei Konstruktoren verfügbar macht.Der Standardkonstruktor sollte verwendet werden, wenn eine neue Instanz erfordert wird, während der Konstruktor, eine ausgeglichene Instanz als Argument annimmt, in den Konstruktorshims nur verwendet werden soll:

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

Die generierte Typstruktur von ShimMyClass ähnelt dem followoing Code:

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

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

Basismember

Auf die Shimsigenschaften von Basismembern Member kann zugegriffen werden, indem ein Shim für den Basistyp erstellt und die untergeordnete Instanz als Parameter für den Konstruktor der Basisklasse Shimklasse übergibt.

Beispiel einer MyBase-Klasse mit einer Instanzmethode MyMethod und einen Untertyp MyChild angegeben:

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

public class MyChild : MyBase {
}

Wir können einen Shim von MyBase installieren, indem Sie einen neuen ShimMyBase Shim erstellen:

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

Beachten Sie, dass die untergeordnete Shimtyp implizit zur untergeordneten Instanz konvertiert wird, wenn er als Parameter im Basispfad Shimkonstruktor übergeben wird.

Die generierte Typstruktur von ShimMyChild und von ShimMyBase ähnelt dem folgenden 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 { ... } }
}

Statische Konstruktoren

Shimtypen machen eine statische Methode StaticConstructor verfügbar, die den statischen Konstruktor eines Typs.Wenn statische Konstruktoren nur einmal ausgeführt werden, Sie müssen sicherstellen, dass der Shim konfiguriert ist, bevor auf jeden Member des Typs zugegriffen wird.

Finalizer

Finalizer werden nicht in Fälschungen unterstützt.

Private-Methoden

Der Fälschungscode-generator erstellt Shimsigenschaften für private Methoden, die nur sichtbares Typen in der Signatur, die z. B. Parametertypen und Rückgabetyp haben, die sichtbar sind.

Binden von Schnittstellen

Wenn ein ausgeglichener Typ eine Schnittstelle implementiert, gibt der Code-Generator einer Methode, die es ermöglicht, alle Member dieser Schnittstelle sofort zu binden.

Beispielsweise einer Klasse MyClass angegeben, die IEnumerable<int> implementiert:

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

Wir können die Implementierungen von IEnumerable<int> in MyClass ausgleichen, indem wir die Bindungsmethode aufrufen:

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

Die generierte Typstruktur von ShimMyClass ähnelt dem folgenden Code:

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

Ändern des Standardverhaltens

Jeder generierte Shimtyp enthält eine Instanz der IShimBehavior-Schnittstelle, durch die ShimBase<T>.InstanceBehavior-Eigenschaft an.Das Verhalten wird verwendet, wenn ein Client einen Instanzmember aufgerufen wird, der nicht explizit versetzt wurde.

Wenn das Verhalten nicht explizit festgelegt wurde, wird die Instanz, die von der statischen ShimsBehaviors.Current-Eigenschaft zurückgegeben wird.Standardmäßig gibt diese Eigenschaft einem Verhalten zurück, das eine NotImplementedException Ausnahme auslöst.

Dieses Verhalten können jederzeit geändert werden, indem die InstanceBehavior-Eigenschaft auf jeder Shiminstanz festlegt.Beispielsweise ändert der folgende Ausschnitt der Shim zu einem Verhalten, die keine Aktion ausführt oder den Standardwert Resultsets, Typ-dass ist, Standard zurückgibt (T):

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

Das Verhalten kann für alle ausgeglichenen Instanzen auch global geändert werden, für die die InstanceBehavior-Eigenschaft nicht explizit festgelegt wurde, indem die statische ShimsBehaviors.Current-Eigenschaft fest:

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

Erkennen von Umgebungszugriffen

Es ist möglich, ein Verhalten auf alle Member, einschließlich Methoden, eines bestimmten Typs anzufügen, indem das ShimsBehaviors.NotImplemented Verhalten zur statischen Eigenschaft Behavior des entsprechenden Shimtyps zugewiesen wird:

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

Parallelität

Shimtypen gelten für alle Threads in der AppDomain zu und weisen keine Threadaffinität auf.Dies ist ein wichtiger Umstand, wenn Sie planen, einen Test Runner verwenden, die die Parallelität unterstützen: die Tests, die Shimtypen einbeziehen, können nicht gleichzeitig ausgeführt werden.Diese Eigenschaft enfored nicht durch die Ausführung Fälschungen.

Aufrufen der ursprünglichen Methode von der Shimmethode

Angenommen, wir den Text am Dateisystem tatsächlich schreiben möchten, nachdem Sie den Dateinamen überprüft haben, der an die Methode übergeben wird.In diesem Fall könnten wir die ursprüngliche Methode in der Mitte der Shimmethode aufrufen werden.

Der erste Ansatz, um dieses Problem zu lösen, ist ein Aufruf der ursprünglichen Methode mithilfe eines Delegaten und ShimsContext.ExecuteWithoutShims() wie im folgenden Code verwendet:

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

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

Ein anderer Ansatz ist, den Shim auf NULL festlegen, die ursprüngliche Methode aufzurufen und den Shim wiederherzustellen.

// 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;

Einschränkungen

Shims können nicht auf allen Typen von der .NET-Basisklassenbibliothek mscorlib und System verwendet werden.

Externe Ressourcen

Empfehlungen

Tests für fortlaufende Übermittlung mit Visual Studio 2012 – Kapitel 2: Komponententests – Interne Tests

Siehe auch

Konzepte

Isolieren von getestetem Code mithilfe von Microsoft Fakes

Weitere Ressourcen

Das Peter-Hochschulleiters Blog: Visual Studio 2012-Shims

Video (1h16): Testen des UN-testfähigen Code mit Fälschungen in Visual Studio 2012