Stosowanie podkładek do izolowania aplikacji od innych zestawów w celu przeprowadzania testów jednostkowych

Podkładki typy są jednym z dwóch technologii używanych w ramach sfałszować firmy Microsoft do pozwalają łatwo Izoluj składników badanego ze środowiska.Podkładki przekazywanie połączeń do szczególnych metod do kodu napisanego w ramach testu.Wiele metod zwracać różne wyniki zależne od warunków zewnętrznych, ale podkładka jest pod kontrolą test i może zwracać spójne wyniki przy każdym wywołaniu.To sprawia, że testy znacznie ułatwia pisanie.

Za pomocą podkładek wykrywać kodu od zespołów, które nie są częścią rozwiązania.Aby wyizolować składniki rozwiązania od siebie, zaleca się używanie procedur wejścia.

Omówienie i szybki start wskazówki zobaczIzolowanie testowanego kodu za pomocą struktury Microsoft Fakes

Wymagania

  • Visual Studio Ultimate

Zobacz wideo (1 h 16): kod sprawdzający Un-testable z podrobionych w Visual Studio 2012 r.

W tym temacie

W tym temacie można dowiedzieć się:

Przykład: Usterka Y2K

Jak używać podkładki

  • Dodać podrobionych zestawów

  • Użyj ShimsContext

  • Pisać testy z podkładki

Podkładki dla różnych rodzajów metod

Zmienianie zachowania domyślnego

Wykrywanie dostępów do środowiska

Współbieżność

Wywołanie oryginalnej metody z metody zastępczej

Ograniczenia

Przykład: Usterka Y2K

Rozważmy metodę, która zgłasza wyjątek w dniu 1 stycznia 2000:

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

Testowanie tej metody jest szczególnie problematyczne, ponieważ program zależy od DateTime.Now, metoda, która zależy od zegara komputera, jest zależną od środowiska, niedeterministyczną metodą.Ponadto DateTime.Now to właściwość statyczna, więc nie można tutaj wykorzystać typu zastępczego.Ten problem jest objawem problemu izolacji jednostki badań: programy, które bezpośrednio wywołują interfejsy API bazy danych, komunikują się z usługami sieci web i tak dalej, są trudne do testowania jednostkowego, ponieważ ich logika zależy od środowiska.

Należy tutaj użyć typów zastępczych.Typy zastępcze zapewniają mechanizm pozwalający przekierować dowolną metodę .NET do zdefiniowanego przez użytkownika delegata.Typy zastępcze są wygenerowanym kodem przez generator Fakes i używają delegatów, które nazywamy typami zastępczymi, aby określić nowe implementacje metod.

Następujący test pokazuje, jak użyć typu zastępczego ShimDateTime, aby zapewnić niestandardową implementację 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();
}

Jak używać podkładki

Dodać podrobionych zestawów

  1. W oknie Solution Explorer rozwiń projekt testu jednostki odwołania.

    • Jeśli pracujesz w języku Visual Basic, należy wybrać Pokaż wszystkie pliki na pasku narzędziowym panelu Solution Explorer, aby wyświetlić listę odwołania.
  2. Wybierz zestaw, który zawiera definicje klas, dla których chcesz utworzyć podkładki.Na przykład jeśli do podkładki daty/godziny, zaznacz opcję System.dll

  3. W menu skrótów wybierz polecenie Dodać sfałszować zestawu.

Użyj ShimsContext

Podczas korzystania z typów zastępczych we frameworku testów jednostkowych, trzeba owinąć kodu testu w ShimsContext, aby kontrolować okres istnienia zastąpień.Jeśli nie byłoby to wymagane, typy zastępcze trwały by do czasu zamknięcia elementu AppDomain.Najprostszym sposobem utworzenia ShimsContext jest użycie statycznej metody Create(), tak jak pokazano w poniższym kodzie:

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

Właściwa likwidacja każdego kontekstu zastępowania jest krytyczna.Zgodnie z praktyką, należy zawsze wywoływać ShimsContext.Create wewnątrz instrukcji using w celu zapewnienia właściwego czyszczenia zarejestrowanych zastąpień.Na przykład, należy zarejestrować zastąpienie dla metody testowej, która zastępuje metodę DateTime.Now za pomocą delegata, który zawsze zwraca 1 stycznia 2000.Jeśli zapomni się wyczyść zarejestrowane zastąpienie w metodzie testowej, pozostałe testy zawsze zwrócą wartość 1 stycznia 2000 jako DateTime.Now.Może to być zaskakujące i kłopotliwe.

Napisać test z podkładki

W kodzie testu wstawić objazd dla metody ma być fałszywe.Na przykład:

[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

Nazwy klas podkładka składają się dodając Fakes.Shim do oryginalnej nazwy typu.

Podkładki pracy przez wstawienie objazdów do kodu aplikacji badany.Wszędzie tam, gdzie występuje, wywołanie metody oryginalnego, system podrobionych wykonuje okrężną drogą, tak, aby zamiast wywołanie metody prawdziwe, nosi nazwę kodu poprawka zgodności aplikacji.

Należy zauważyć, że objazdów są tworzone i usuwane w czasie wykonywania.Należy zawsze utworzyć objazd w życie ShimsContext.Kiedy jest usuwane, podkładki, który utworzono, gdy był aktywny są usuwane.Najlepszym sposobem na to znajduje się wewnątrz using instrukcji.

Może pojawić się błąd z kompilacji stwierdzające, że podrobionych obszaru nazw nie istnieje.Ten błąd pojawia się czasami, gdy istnieją inne błędy kompilacji.Rozwiąż inne błędy i zniknie.

Podkładki dla różnych rodzajów metod

Typy shim umożliwiają wymianę każdej metody .NET, w tym metody statyczne lub metody innej niż wirtualna, własne delegatów.

Metody statyczne

Właściwości służące do dołączania zastąpień do metod statycznych są umieszczane w typie zastępczym.Każda właściwość ma tylko metodę ustawiającą, używaną do dołączenia delegata do metody docelowej.Na przykład, podana klasa MyClass z metodą statyczną MyMethod:

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

Możemy dołączyć zastąpienie do MyMethod, które zawsze zwraca 5:

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

Metody wystąpienia (dla wszystkich wystąpień)

Podobnie do metod statycznych, metody wystąpienia można zastąpić dla wszystkich wystąpień.Właściwości, do których można dołączyć zastąpienia są umieszczane w typie zagnieżdżonym o nazwie AllInstances, aby uniknąć pomyłek.Na przykład, podana klasa MyClass z metodą wystąpienia MyMethod:

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

Można dołączyć podkładkę, aby MyMethod , które zawsze zwraca 5, bez względu na wystąpienie:

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

Struktura wygenerowanego typu ShimMyClass wygląda podobnie do następującego kodu:

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

Należy zauważyć, że Fakes przekazuje w takim przypadku wystąpienie środowiska uruchomieniowego jako pierwszy argument delegata.

Metody wystąpienia (dla jednego wystąpienia w trakcie wykonywania)

Metody wystąpienia można również zastąpić przez różne delegaty, oparte na odbiorcy wywołania.Pozwala to tej samej metodzie wystąpienia posiadać różne zachowania dla każdego wystąpienia tego typu.Właściwości służące do dołączania zastąpień do metod wystąpienia są umieszczane w typie zastępczym.Każdy skonkretyzowany typ zastępczy jest również skojarzony z surowym wystąpieniem typu zastępczego.

Na przykład, podana klasa MyClass z metodą wystąpienia MyMethod:

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

Można ustawić dwa typy zastępcze MyMethod, takie aby pierwszy zawsze zwracał 5 i drugi zawsze zwracał 10:

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

Struktura wygenerowanego typu ShimMyClass wygląda podobnie do następującego kodu:

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

Dostęp do rzeczywistego wystąpienia typu zastępczego jest możliwy za pośrednictwem właściwości instancji:

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

Typ zastępczy ma również niejawną konwersję typu zastępczego, więc można użyć typu zastępczego bezpośrednio:

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

Konstruktory

Konstruktory mogą również być zastąpione w celu dołączania typów zastępczych do obiektów w przyszłości.Każdy konstruktor jest ujawniony jako statyczna metoda konstruktora typu zastępczego.Na przykład, podana klasa MyClass za pomocą konstruktora, pobierającego liczbę całkowitą:

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

Można zdefiniować typ zastępczy konstruktora tak, aby każde wystąpienie w przyszłości zwracało -5, gdy metoda pobierająca jest wywoływana, niezależnie od wartości w konstruktorze:

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

Należy zauważyć, że każdy typ zastępczy udostępnia dwa konstruktory.Domyślny konstruktor powinien być używany, gdy potrzebne jest świeże wystąpienie, podczas gdy konstruktor pobierający wystąpienie zastępcze jako argument, powinien być używany w zastąpieniach konstruktora:

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

Struktura wygenerowanego typu ShimMyClass przypomina następujący kod:

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

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

Podstawowe elementy członkowskie

Właściwości zastępcze podstawowych elementów członkowskich są dostępne przez utworzenie zastąpienia dla typu zastępczego i przekazanie wystąpienia podrzędnego jako parametru do konstruktora podstawowej klasy zastępczej.

Na przykład, podana klasa MyBase z metodą wystąpienia MyMethod i podtypem MyChild:

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

public class MyChild : MyBase {
}

Można zdefiniować zastąpienie MyBase przez utworzenie nowego zastąpienia ShimMyBase:

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

Należy zauważyć, że podrzędny typ zastępczy jest niejawnie konwertowany do wystąpienia podrzędnego, gdy jest przekazany jako parametr do podstawowego konstruktora zastąpienia.

Struktury wygenerowanych typów typu ShimMyChild i ShimMyBase przypominają następujący kod:

// 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 { ... } }
}

Konstruktory statyczne

Typy zastępcze udostępniają statyczną metodę StaticConstructor, aby zastąpić statyczny konstruktor typu.Ponieważ konstruktory statyczne są wykonywane tylko raz, należy upewnić się, że zastąpienie jest skonfigurowane przed uzyskaniem dostępu do dowolnego elementu członkowskiego typu.

Finalizatory

Finalizatory nie są obsługiwane w Fakes.

Metody prywatne

Generator kodu Fakes utworzy zastąpienia właściwości tylko dla tych metod prywatnych, które mają widoczne typy w sygnaturze, n.p. widoczne typy parametrów i typ zwracany.

Wiązanie interfejsów

Gdy typ zastępczy implementuje interfejs, generator kodu emituje metodę umożliwiająca powiązanie wszystkich elementów tego interfejsu jednocześnie.

Na przykład, podana klasa MyClass, która implementuje IEnumerable<int>:

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

Możemy zastąpić implementację IEnumerable<int> w MyClass, wywołując metodę Bind:

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

Struktura wygenerowanego typu ShimMyClass przypomina następujący kod:

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

Zmienianie zachowania domyślnego

Każdy wygenerowany typ zastępczy posiada wystąpienie interfejsu IShimBehavior poprzez właściwość ShimBase<T>.InstanceBehavior.Zachowanie jest używane zawsze, gdy klient wywołuje element członkowski wystąpienia, które nie zostało jawnie zastąpione.

Jeśli zachowanie nie zostanie jawnie ustawione, używane będzie wystąpienie zwracane przez statyczną właściwość ShimsBehaviors.Current.Domyślnie właściwość ta zwraca zachowanie, które zgłasza wyjątek NotImplementedException.

To zachowanie można zmienić w dowolnym czasie przez ustawienie właściwości InstanceBehavior dowolnego wystąpienia zastąpienia.Na przykład, poniższa wstawka zmienia zachowanie zastąpienia, tak aby nic nie robiło lub zwracało wartość domyślną zwracanego typu — czyli default(T):

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

Zachowanie można zmienić globalnie dla wszystkich wystąpień zastąpień dla których właściwość InstanceBehavior nie została jawnie ustawiona przez ustawienie statycznej właściwości ShimsBehaviors.Current:

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

Wykrywanie dostępów do środowiska

Możliwe jest dołączenie zachowania do wszystkich elementów członkowskich, w tym metod statycznych określonego typu, przypisując zachowanie ShimsBehaviors.NotImplemented do statycznej właściwości Behavior odpowiadającego typu zastępczego:

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

Współbieżność

Typy zastępcze stosuje się do wszystkich wątków w AppDomain i nie występują kolizje wątków.Jest to istotny fakt, jeśli planuje się uruchamiać testy, które obsługują współbieżność: testy angażujące typy zastępcze nie mogą być uruchamiane jednocześnie.Ta właściwość nie jest narzucana przez środowisko wykonawcze Fakes.

Wywołanie oryginalnej metody z metody zastępczej

Wyobraźmy sobie, że trzeba zapisać tekst do systemu plików po sprawdzaniu poprawności nazwy pliku przekazanego do metody.W takim przypadku trzeba wywołać oryginalną metodę w środku metody zastępczej.

Pierwszym podejściem do rozwiązania tego problemu jest owinięcie wywoływanej oryginalnej metody, korzystając z delegata i ShimsContext.ExecuteWithoutShims() zgodnie z poniższym kodem:

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

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

Innym podejściem jest ustawienie zastąpienia na null, wywołanie oryginalnej metody i przywrócenie zastąpienia.

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

Ograniczenia

Zastąpienia nie mogą być używane do wszystkich typów z podstawowej biblioteki klas .NET mscorlib i System.

Zasoby zewnętrzne

Wskazówki

Badania na nieprzerwane z Visual Studio 2012-rozdział 2: Testowanie jednostek: testowanie wewnątrz

Zobacz też

Koncepcje

Izolowanie testowanego kodu za pomocą struktury Microsoft Fakes

Inne zasoby

Peterowi Provost blogu: Visual Studio 2012 podkładki

wideo (1 h 16): kod sprawdzający Un-testable z podrobionych w Visual Studio 2012 r.