Stosowanie wycinków kodu do izolowania od siebie poszczególnych części aplikacji w celu przeprowadzania testów jednostkowych

Typy wycinków są ważną technologią dostarczaną przez platformę Microsoft Fakes, umożliwiając łatwą izolację składnika testowego z innych składników, na których opiera się. Wycinkę pełni rolę małego fragmentu kodu, który zastępuje inny składnik podczas testowania. Kluczową zaletą korzystania z wycinków jest możliwość uzyskania spójnych wyników, aby ułatwić pisanie testów. Nawet jeśli inne składniki nie są jeszcze w pełni funkcjonalne, nadal można wykonywać testy przy użyciu wycinków.

Aby skutecznie zastosować wycinki, zaleca się zaprojektowanie składnika w sposób, który zależy przede wszystkim od interfejsów, a nie konkretnych klas z innych części aplikacji. Takie podejście projektowe promuje oddzielenie i zmniejsza prawdopodobieństwo zmian w jednej części wymagającej modyfikacji w innej. Jeśli chodzi o testowanie, ten wzorzec projektu umożliwia podstawianie implementacji wycinków dla rzeczywistego składnika, ułatwiając skuteczną izolację i dokładne testowanie składnika docelowego.

Rozważmy na przykład diagram ilustrujący zaangażowane składniki:

Diagram of Real and Stub classes of StockAnalyzer.

Na tym diagramie składnik testowy to StockAnalyzer, który zwykle opiera się na innym składniku o nazwie RealStockFeed. Jednak stanowi wyzwanie dla testowania, RealStockFeed ponieważ zwraca różne wyniki za każdym razem, gdy metody są wywoływane. Ta zmienność utrudnia zapewnienie spójnych i niezawodnych testów programu StockAnalyzer.

Aby pokonać tę przeszkodę podczas testowania , możemy przyjąć praktykę wstrzykiwania zależności. Takie podejście polega na pisaniu kodu w taki sposób, że nie wymienia jawnie klas w innym składniku aplikacji. Zamiast tego należy zdefiniować interfejs, który inny składnik i wycink mogą implementować do celów testowych.

Oto przykład użycia iniekcji zależności w kodzie:

public int GetContosoPrice(IStockFeed feed) => feed.GetSharePrice("COOO");

Ograniczenia dotyczące wycinka

Zapoznaj się z następującymi ograniczeniami dotyczącymi wycinków.

Tworzenie wycinków: przewodnik krok po kroku

Zacznijmy od tego ćwiczenia z motywującym przykładem: pokazanym na powyższym diagramie.

Tworzenie biblioteki klas

Wykonaj następujące kroki, aby utworzyć bibliotekę klas.

  1. Otwórz program Visual Studio i utwórz projekt Biblioteka klas.

    Screenshot of Class Library project in Visual Studio.

  2. Skonfiguruj atrybuty projektu:

    • Ustaw nazwę projektu na StockAnalysis.
    • Ustaw nazwę rozwiązania na StubsTutorial.
    • Ustaw platformę docelową projektu na .NET 8.0.
  3. Usuń plik domyślny Class1.cs.

  4. Dodaj nowy plik o nazwie IStockFeed.cs i skopiuj go w następującej definicji interfejsu:

    // IStockFeed.cs
    public interface IStockFeed
    {
        int GetSharePrice(string company);
    }
    
  5. Dodaj kolejny nowy plik o nazwie StockAnalyzer.cs i skopiuj go w następującej definicji klasy:

    // StockAnalyzer.cs
    public class StockAnalyzer
    {
        private IStockFeed stockFeed;
        public StockAnalyzer(IStockFeed feed)
        {
            stockFeed = feed;
        }
        public int GetContosoPrice()
        {
            return stockFeed.GetSharePrice("COOO");
        }
    }
    

Tworzenie projektu testowego

Utwórz projekt testowy dla ćwiczenia.

  1. Kliknij rozwiązanie prawym przyciskiem myszy i dodaj nowy projekt o nazwie MSTest Test Project.

  2. Ustaw nazwę projektu na TestProject.

  3. Ustaw platformę docelową projektu na .NET 8.0.

    Screenshot of Test project in Visual Studio.

Dodawanie zestawu Fakes

Dodaj zestaw Fakes dla projektu.

  1. Dodaj odwołanie do projektu do StockAnalyzer.

    Screenshot of the command Add Project Reference.

  2. Dodaj zestaw Fakes.

    1. W Eksplorator rozwiązań znajdź odwołanie do zestawu:

      • W przypadku starszego projektu .NET Framework (bez zestawu SDK) rozwiń węzeł Odwołania projektu testów jednostkowych .

      • W przypadku projektu w stylu zestawu SDK przeznaczonego dla platformy .NET Framework, .NET Core lub .NET 5.0 lub nowszego rozwiń węzeł Zależności , aby znaleźć zestaw, który chcesz sfałszować w obszarze Zestawy, Projekty lub Pakiety.

      • Jeśli pracujesz w języku Visual Basic, wybierz pozycję Pokaż wszystkie pliki na pasku narzędzi Eksplorator rozwiązań, aby wyświetlić węzeł Odwołania.

    2. Wybierz zestaw zawierający definicje klas, dla których chcesz utworzyć wycinki.

    3. W menu skrótów wybierz pozycję Dodaj zestaw fakes.

      Screenshot of the command Add Fakes Assembly.

Tworzenie testu jednostkowego

Teraz utwórz test jednostkowy.

  1. Zmodyfikuj domyślny plik UnitTest1.cs , aby dodać następującą Test Method definicję.

    [TestClass]
    class UnitTest1
    {
        [TestMethod]
        public void TestContosoPrice()
        {
            // Arrange:
            int priceToReturn = 345;
            string companyCodeUsed = "";
            var componentUnderTest = new StockAnalyzer(new StockAnalysis.Fakes.StubIStockFeed()
            {
                GetSharePriceString = (company) =>
                {
                    // Store the parameter value:
                    companyCodeUsed = company;
                    // Return the value prescribed by this test:
                    return priceToReturn;
                }
            });
    
            // Act:
            int actualResult = 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);
        }
    }
    

    Specjalny kawałek magii to StubIStockFeed klasa. Dla każdego interfejsu w zestawie, do którego istnieje odwołanie, mechanizm Microsoft Fakes generuje klasę zastępczą. Nazwa klasy wycinkowej pochodzi z nazwy interfejsu z ciągiem "Fakes.Stub" jako prefiksem, a nazwy typów parametrów są dołączane.

    Wycinki kodu są generowane także dla metod pobierających i ustawiających właściwości, dla zdarzeń i metod ogólnych. Aby uzyskać więcej informacji, zobacz Używanie wycinków do izolowania części aplikacji od siebie na potrzeby testowania jednostkowego.

    Screenshot of Solution Explorer showing all files.

  2. Otwórz Eksploratora testów i uruchom test.

    Screenshot of Test Explorer.

Wycinki dla różnych rodzajów elementów członkowskich typu

Istnieją wycinki dla różnych rodzajów składowych typu.

Metody

W podanym przykładzie metody mogą być wycięty przez dołączenie delegata do wystąpienia klasy wycinkowej. Nazwa typu wycinka pochodzi od nazwy metody i parametrów. Rozważmy na przykład następujący IStockFeed interfejs i jego metodę GetSharePrice:

// IStockFeed.cs
interface IStockFeed
{
    int GetSharePrice(string company);
}

Dołączamy wycinkę do GetSharePrice elementu przy użyciu polecenia GetSharePriceString:

// unit test code
var componentUnderTest = new StockAnalyzer(new StockAnalysis.Fakes.StubIStockFeed()
        {
            GetSharePriceString = (company) =>
            {
                // Store the parameter value:
                companyCodeUsed = company;
                // Return the value prescribed by this test:
                return priceToReturn;
            }
        });

Jeśli nie podasz wycinku dla metody, funkcja Fakes generuje funkcję zwracającą default value typ zwracany. W przypadku liczb wartość domyślna to 0. W przypadku typów klas wartość domyślna to null C# lub Nothing Visual Basic.

Właściwości

Metody pobierające i zestawy właściwości są widoczne jako oddzielne delegaty i mogą być dźgnięte pojedynczo. Rozważmy na przykład Value właściwość IStockFeedWithProperty:

interface IStockFeedWithProperty
{
    int Value { get; set; }
}

Aby wciągnąć metodę pobierającą Value i ustawiającą i symulując automatyczną właściwość, możesz użyć następującego kodu:

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

Jeśli nie udostępniasz metod wycinków dla klasy setter lub getter właściwości, Fakes generuje wycinkę, która przechowuje wartości, dzięki czemu właściwość wycinków zachowuje się jak prosta zmienna.

Wydarzenia

Zdarzenia są ujawniane jako pola delegatów, dzięki czemu każde zdarzenie oszołomione może być wywoływane po prostu przez wywołanie pola kopii zapasowej zdarzenia. Rozważmy następujący interfejs, aby utworzyć wycinkę:

interface IStockFeedWithEvents
{
    event EventHandler Changed;
}

Aby zgłosić Changed zdarzenie, należy wywołać delegata obsługi zapasowej:

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

Metody ogólne

Metody ogólne można wciągnieć, podając delegata dla każdej żądanej wystąpienia metody. Na przykład, biorąc pod uwagę następujący interfejs z metodą ogólną:

interface IGenericMethod
{
    T GetValue<T>();
}

Wystąpienie można utworzyć GetValue<int> w następujący sposób:

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

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

Jeśli kod wywołuje GetValue<T> inne wystąpienie, element wycinkowy wykonuje zachowanie.

Wycinki wirtualnych klas

W poprzednich przykładach wycinki zostały wygenerowane z interfejsów. Można jednak również wygenerować wycinki z klasy, która ma wirtualne lub abstrakcyjne elementy członkowskie. Na przykład:

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

W wycinku wygenerowanych na podstawie tej klasy można ustawić metody delegatów dla DoAbstract() elementów i DoVirtual(), ale nie DoConcrete().

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

Jeśli nie podasz delegata dla metody wirtualnej, fakes może dostarczyć domyślne zachowanie lub wywołać metodę w klasie bazowej. Aby wywołać metodę podstawową CallBase , ustaw właściwość :

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

Zmienianie domyślnego zachowania wycinków

Każdy wygenerowany typ wycinków przechowuje wystąpienie interfejsu IStubBehaviorIStub.InstanceBehavior za pośrednictwem właściwości . To zachowanie jest wywoływane za każdym razem, gdy klient wywołuje element członkowski bez dołączonego delegata niestandardowego. Jeśli zachowanie nie jest ustawione, używa wystąpienia zwróconego StubsBehaviors.Current przez właściwość . Domyślnie ta właściwość zwraca zachowanie, które zgłasza NotImplementedException wyjątek.

Zachowanie można zmienić w dowolnym momencie, ustawiając właściwość w dowolnym wystąpieniu InstanceBehavior wycinkowym. Na przykład poniższy fragment kodu zmienia zachowanie, tak aby wycinkę nie zrobiła nic lub zwróciła wartość domyślną zwracanego typu default(T):

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

Zachowanie można również zmienić globalnie dla wszystkich obiektów wycinków, w których zachowanie nie jest ustawione za StubsBehaviors.Current pomocą właściwości :

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