Vzájemná izolace částí aplikace pomocí zástupných procedur za účelem testování částí
Typy zástupných procedur jsou důležitou technologií poskytovanou architekturou Microsoft Fakes, která umožňuje snadnou izolaci komponenty, kterou testujete od ostatních komponent, na kterých spoléhá. Zástupný kód funguje jako malý kód, který během testování nahrazuje jinou komponentu. Klíčovou výhodou používání zástupných procedur je možnost získat konzistentní výsledky, aby bylo psaní testů jednodušší. I když ostatní komponenty ještě nejsou plně funkční, můžete testy stále spouštět pomocí zástupných procedur.
Pokud chcete efektivně použít zástupné procedury, doporučujeme navrhnout komponentu způsobem, který primárně závisí na rozhraních, nikoli na konkrétních třídách z jiných částí aplikace. Tento přístup návrhu podporuje oddělení a snižuje pravděpodobnost změn v jedné části, které vyžadují změny v jiné. Pokud jde o testování, tento vzor návrhu umožňuje nahradit implementaci zástupných procedur pro skutečnou komponentu, což usnadňuje efektivní izolaci a přesné testování cílové komponenty.
Podívejme se například na diagram, který znázorňuje zahrnuté komponenty:
V tomto diagramu je komponenta pod testem StockAnalyzer
, která obvykle spoléhá na jinou komponentu volanou RealStockFeed
. Představuje ale výzvu pro testování, protože pokaždé, když se volají metody, RealStockFeed
vrací různé výsledky. Tato variabilita znesnadňuje zajištění konzistentního a spolehlivého StockAnalyzer
testování .
Abychom tuto překážku během testování překonat, můžeme přijmout praxi injektáže závislostí. Tento přístup zahrnuje psaní kódu takovým způsobem, že explicitně nezmíní třídy v jiné komponentě vaší aplikace. Místo toho definujete rozhraní, které může druhá komponenta a zástupný kód implementovat pro testovací účely.
Tady je příklad použití injektáže závislostí v kódu:
Omezení zástupných procedur
Projděte si následující omezení pro zástupné procedury.
Podpisy metody s ukazateli nejsou podporované.
Zapečetěné třídy nebo statické metody nelze zatěžovat pomocí typů zástupné procedury, protože typy zástupné procedury spoléhají na odesílání virtuálních metod. V takových případech použijte typy shim, jak je popsáno v tématu Použití shimů k izolaci aplikace od jiných sestavení pro testování jednotek.
Vytvoření zástupných procedur: Podrobný průvodce
Začněme tímto cvičením s motivačním příkladem: příkladem, který je znázorněný v předchozím diagramu.
Vytvoření knihovny tříd
Pokud chcete vytvořit knihovnu tříd, postupujte podle těchto kroků.
Otevřete Visual Studio a vytvořte projekt knihovny tříd.
Konfigurace atributů projektu:
- Nastavte název projektu na StockAnalysis.
- Nastavte název řešení na StubsTutorial.
- Nastavte cílovou architekturu projektu na .NET 8.0.
Odstraňte výchozí soubor Class1.cs.
Přidejte nový soubor S názvem IStockFeed.cs a zkopírujte do následující definice rozhraní:
Přidejte další nový soubor s názvem StockAnalyzer.cs a zkopírujte do následující definice třídy:
Vytvoření testovacího projektu
Vytvořte testovací projekt pro cvičení.
Klikněte pravým tlačítkem na řešení a přidejte nový projekt s názvem MSTest Test Project.
Nastavte název projektu na TestProject.
Nastavte cílovou architekturu projektu na .NET 8.0.
Přidání sestavení Fakes
Přidejte sestavení Fakes pro projekt.
Přidejte odkaz na projekt .
StockAnalyzer
Přidejte sestavení Fakes.
V Průzkumník řešení vyhledejte odkaz na sestavení:
Pro starší projekt rozhraní .NET Framework (styl bez sady SDK) rozbalte uzel Reference projektu testů jednotek.
Pro projekt ve stylu sady SDK, který cílí na rozhraní .NET Framework, .NET Core nebo .NET 5.0 nebo novější, rozbalte uzel Závislosti a vyhledejte sestavení, které chcete zfalšovat v rámci sestavení, projektů nebo balíčků.
Pokud pracujete v jazyce Visual Basic, vyberte Zobrazit všechny soubory na panelu nástrojů Průzkumník řešení a zobrazte uzel Reference.
Vyberte sestavení obsahující definice tříd, pro které chcete vytvořit zástupné procedury.
V místní nabídce vyberte Přidat falešné sestavení.
Vytvoření testu jednotek
Teď vytvořte test jednotek.
Upravte výchozí soubor UnitTest1.cs a přidejte následující
Test Method
definici.[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); } }
Zvláštní magie tady je
StubIStockFeed
třída. Pro každé rozhraní v odkazovaném sestavení generuje mechanismus rozhraní Microsoft Fakes zástupnou třídu. Název třídy zástupných procedur je odvozen od názvu rozhraní sFakes.Stub
"" jako předpona a názvy typů parametrů připojené.Zástupné procedury jsou také generovány pro mechanismy získání a nastavení vlastností, pro události a pro obecné metody. Další informace najdete v tématu Použití zástupných procedur k izolaci částí aplikace od sebe pro účely testování jednotek.
Otevřete Průzkumníka testů a spusťte test.
Zástupné procedury pro různé druhy členů typu
Existují zástupné procedury pro různé druhy členů typu.
Metody
V uvedeném příkladu lze metody zakrýt připojením delegáta k instanci třídy zástupné procedury. Název typu zástupné procedury je odvozen z názvu metody a parametrů. Představte si například následující IStockFeed
rozhraní a jeho metodu GetSharePrice
:
// IStockFeed.cs
interface IStockFeed
{
int GetSharePrice(string company);
}
Zástupný kód GetSharePrice
připojíme pomocí: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;
}
});
Pokud pro metodu nezadáte zástupný znak, funkce Fakes vygeneruje funkci, která vrátí default value
návratový typ. Pro čísla je výchozí hodnota 0. U typů tříd je null
výchozí hodnota v jazyce C# nebo Nothing
v jazyce Visual Basic.
Vlastnosti
Gettery a settery vlastností jsou vystaveny jako samostatné delegáty a mohou být potaženy jednotlivě. Představte si Value
například vlastnost IStockFeedWithProperty
:
interface IStockFeedWithProperty
{
int Value { get; set; }
}
Chcete-li zaostřit na getter a setter a Value
simulovat automatickou vlastnost, můžete použít následující kód:
// unit test code
int i = 5;
var stub = new StubIStockFeedWithProperty();
stub.ValueGet = () => i;
stub.ValueSet = (value) => i = value;
Pokud neposkytujete metody zástupných procedur pro setter nebo getter vlastnosti, Fakes generuje zástupný znak, který ukládá hodnoty, takže vlastnost zástupných procedur se chová jako jednoduchá proměnná.
Událost
Události se zveřejňují jako pole delegáta, což umožňuje, aby se jakákoli událost vyvolala jednoduše vyvoláním pole backingu události. Pojďme se podívat na následující rozhraní pro zástupný kód:
interface IStockFeedWithEvents
{
event EventHandler Changed;
}
Pokud chcete vyvolat Changed
událost, vyvoláte záložního delegáta:
// unit test code
var withEvents = new StubIStockFeedWithEvents();
// raising Changed
withEvents.ChangedEvent(withEvents, EventArgs.Empty);
Obecné metody
Obecné metody zástupných procedur můžete zadat delegátem pro každou požadovanou instanci metody. Například vzhledem k následujícímu rozhraní s obecnou metodou:
interface IGenericMethod
{
T GetValue<T>();
}
Vytvoření instance můžete nasmát GetValue<int>
následujícím způsobem:
[TestMethod]
public void TestGetValue()
{
var stub = new StubIGenericMethod();
stub.GetValueOf1<int>(() => 5);
IGenericMethod target = stub;
Assert.AreEqual(5, target.GetValue<int>());
}
Pokud kód volá GetValue<T>
s jakoukoli jinou instancí, procedura chování provede.
Zástupné procedury virtuálních tříd
V předchozích příkladech byly zástupné procedury vytvořeny z rozhraní. Můžete však také generovat zástupné procedury z třídy, která má virtuální nebo abstraktní členy. Příklad:
// 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;
}
}
V zástupných procedur vygenerovaných z této třídy můžete nastavit metody delegáta pro DoAbstract()
, DoVirtual()
ale ne DoConcrete()
.
// unit test
var stub = new Fakes.MyClass();
stub.DoAbstractString = (x) => { Assert.IsTrue(x>0); };
stub.DoVirtualInt32 = (n) => 10 ;
Pokud nezadáte delegáta pro virtuální metodu, fakes může buď poskytnout výchozí chování, nebo volat metodu v základní třídě. Chcete-li, aby byla volána základní metoda, nastavte CallBase
vlastnost:
// 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));
Změna výchozího chování zástupných procedur
Každý vygenerovaný typ zástupných procedur obsahuje instanci IStubBehavior
rozhraní prostřednictvím IStub.InstanceBehavior
vlastnosti. Toto chování se volá pokaždé, když klient zavolá člena bez připojeného vlastního delegáta. Pokud chování není nastaveno, použije instanci vrácenou StubsBehaviors.Current
vlastností. Ve výchozím nastavení tato vlastnost vrací chování, které vyvolá NotImplementedException
výjimku.
Chování můžete kdykoli změnit nastavením InstanceBehavior
vlastnosti na libovolnou instanci zástupných procedur. Například následující fragment kódu změní chování tak, aby zástupný znak buď nic neudělal, nebo vrátil výchozí hodnotu návratového typu default(T)
:
// unit test code
var stub = new StockAnalysis.Fakes.StubIStockFeed();
// return default(T) or do nothing
stub.InstanceBehavior = StubsBehaviors.DefaultValue;
Toto chování lze také globálně změnit pro všechny objekty zástupných procedur, u kterých není chování nastaveno s StubsBehaviors.Current
vlastností:
// Change default behavior for all stub instances where the behavior has not been set.
StubBehaviors.Current = BehavedBehaviors.DefaultValue;