Usare stub per isolare parti dell'applicazione l'una dall'altra per il testing unità
I tipi Stub sono una tecnologia importante fornita dal framework Microsoft Fakes, consentendo un facile isolamento del componente che si sta testando da altri componenti su cui si basa. Uno stub funge da piccola parte di codice che sostituisce un altro componente durante il test. Un vantaggio fondamentale dell'uso degli stub è la possibilità di ottenere risultati coerenti per semplificare la scrittura dei test. Anche se gli altri componenti non sono ancora completamente funzionanti, è comunque possibile eseguire test usando stub.
Per applicare gli stub in modo efficace, è consigliabile progettare il componente in modo che dipende principalmente da interfacce anziché classi concrete di altre parti dell'applicazione. Questo approccio di progettazione promuove il disaccoppiamento e riduce la probabilità di modifiche in una parte che richiede modifiche in un'altra. Quando si tratta di test, questo modello di progettazione consente di sostituire un'implementazione di stub per un componente reale, semplificando l'isolamento efficace e il test accurato del componente di destinazione.
Si consideri ad esempio il diagramma che illustra i componenti coinvolti:
In questo diagramma il componente sottoposto a test è StockAnalyzer
, che in genere si basa su un altro componente denominato RealStockFeed
. Tuttavia, RealStockFeed
rappresenta una sfida per i test perché restituisce risultati diversi ogni volta che vengono chiamati i relativi metodi. Questa variabilità rende difficile garantire test coerenti e affidabili di StockAnalyzer
.
Per superare questo ostacolo durante i test, è possibile adottare la pratica di inserimento delle dipendenze. Questo approccio implica la scrittura del codice in modo da non menzionare in modo esplicito le classi in un altro componente dell'applicazione. Si definisce invece un'interfaccia che l'altro componente e uno stub possono implementare a scopo di test.
Di seguito è riportato un esempio di come usare l'inserimento delle dipendenze nel codice:
Limitazioni degli stub
Esaminare le limitazioni seguenti per gli stub.
Le firme di metodo con puntatori non sono supportate.
Le classi sealed o i metodi statici non possono essere stub tramite tipi stub perché i tipi stub si basano sull'invio del metodo virtuale. Per questi casi, usare i tipi shim come descritto in Usare shim per isolare l'applicazione dagli altri assembly per il testing unità
Creazione di uno Stub: guida dettagliata
Si inizierà questo esercizio con un esempio motivante: quello illustrato nel diagramma precedente.
Creare una libreria di classi
Seguire questa procedura per creare una libreria di classi.
Aprire Visual Studio e creare un progetto libreria di classi.
Configurare gli attributi del progetto:
- Impostare Nome progetto su StockAnalysis.
- Impostare Il nome della soluzione su StubsTutorial.
- Impostare framework di destinazione del progetto su .NET 8.0.
Eliminare il file predefinito Class1.cs.
Aggiungere un nuovo file denominato IStockFeed.cs e copiarlo nella definizione di interfaccia seguente:
Aggiungere un altro nuovo file denominato StockAnalyzer.cs e copiare nella definizione di classe seguente:
Creare un progetto di test
Creare il progetto di test per l'esercizio.
Fare clic con il pulsante destro del mouse sulla soluzione e aggiungere un nuovo progetto denominato PROGETTO di test MSTest.
Impostare il nome del progetto su TestProject.
Impostare il framework di destinazione del progetto su .NET 8.0.
Aggiungere l'assembly Fakes
Aggiungere l'assembly Fakes per il progetto.
Aggiungere un riferimento al progetto a
StockAnalyzer
.Aggiungere l'assembly Fakes.
In Esplora soluzioni individuare il riferimento all'assembly:
Per un progetto .NET Framework precedente (stile non SDK), espandere il nodo Riferimenti del progetto di unit test.
Per un progetto in stile SDK destinato a .NET Framework, .NET Core o .NET 5.0 o versione successiva, espandere il nodo Dipendenze per trovare l'assembly che si vuole simulare in Assembly, Progetti o Pacchetti.
Se si usa Visual Basic, selezionare Mostra tutti i file nella barra degli strumenti Esplora soluzioni per visualizzare il nodo Riferimenti.
Selezionare l'assembly contenente le definizioni di classe per cui si desidera creare stub.
Nel menu di scelta rapida selezionare Aggiungi assembly Fakes.
Creare uno unit test
Creare ora lo unit test.
Modificare il file predefinito UnitTest1.cs per aggiungere la definizione seguente
Test Method
.[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); } }
Il pezzo speciale di magia qui è la
StubIStockFeed
classe. Per ogni interfaccia nell'assembly di riferimento, il meccanismo Microsoft Fakes genera una classe stub. Il nome della classe stub deriva dal nome dell'interfaccia, con "Fakes.Stub
" come prefisso e i nomi dei tipi di parametro accodati.Gli stub vengono generati per i metodi GET e SET di proprietà, per gli eventi e per i metodi generici. Per altre informazioni, vedere Usare gli stub per isolare le parti dell'applicazione l'una dall'altra per gli unit test.
Aprire Esplora test ed eseguire il test.
Stub per tipi di membri di tipo differenti
Esistono stub per diversi tipi di membri di tipo.
Metodi
Nell'esempio fornito, i metodi possono essere stub collegando un delegato a un'istanza della classe stub. Il nome del tipo stub è derivato dai nomi del metodo e dei parametri. Si consideri ad esempio l'interfaccia seguente IStockFeed
e il relativo metodo GetSharePrice
:
// IStockFeed.cs
interface IStockFeed
{
int GetSharePrice(string company);
}
Si allega uno stub a GetSharePrice
usando 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;
}
});
Se non si specifica uno stub per un metodo, Fakes genera una funzione che restituisce l'oggetto default value
del tipo restituito. Per i numeri, il valore predefinito è 0. Per i tipi di classe, il valore predefinito è null
in C# o Nothing
in Visual Basic.
Proprietà
I getter e i setter delle proprietà vengono esposti come delegati separati e possono essere stub singolarmente. Ad esempio, si consideri la proprietà Value
di IStockFeedWithProperty
:
interface IStockFeedWithProperty
{
int Value { get; set; }
}
Per stubare il getter e il setter di Value
e simulare una proprietà automatica, è possibile usare il codice seguente:
// unit test code
int i = 5;
var stub = new StubIStockFeedWithProperty();
stub.ValueGet = () => i;
stub.ValueSet = (value) => i = value;
Se non si forniscono metodi stub per il setter o il getter di una proprietà, Fakes genera uno stub che archivia i valori, rendendo la proprietà stub come una variabile semplice.
Eventi
Gli eventi vengono esposti come campi delegati, consentendo la generazione di qualsiasi evento stub semplicemente richiamando il campo sottostante dell'evento. Si consideri la seguente interfaccia da sottoporre a stub:
interface IStockFeedWithEvents
{
event EventHandler Changed;
}
Per generare l'evento Changed
, richiamare il delegato di backup:
// unit test code
var withEvents = new StubIStockFeedWithEvents();
// raising Changed
withEvents.ChangedEvent(withEvents, EventArgs.Empty);
Metodi generici
È possibile eseguire lo stub dei metodi generici fornendo un delegato per ogni istanza desiderata del metodo. Ad esempio, data l'interfaccia seguente con un metodo generico:
interface IGenericMethod
{
T GetValue<T>();
}
È possibile eseguire lo stub della creazione di un'istanza GetValue<int>
come indicato di seguito:
[TestMethod]
public void TestGetValue()
{
var stub = new StubIGenericMethod();
stub.GetValueOf1<int>(() => 5);
IGenericMethod target = stub;
Assert.AreEqual(5, target.GetValue<int>());
}
Se il codice chiama GetValue<T>
con qualsiasi altra istanza, lo stub esegue il comportamento.
Stub di classi virtuali
Negli esempi precedenti gli stub sono stati generati dalle interfacce. Tuttavia, è anche possibile generare stub da una classe con membri virtuali o astratti. Ad esempio:
// 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;
}
}
Nello stub generato da questa classe è possibile impostare i metodi delegati per DoAbstract()
e DoVirtual()
, ma non per DoConcrete()
.
// unit test
var stub = new Fakes.MyClass();
stub.DoAbstractString = (x) => { Assert.IsTrue(x>0); };
stub.DoVirtualInt32 = (n) => 10 ;
Se non si fornisce un delegato per un metodo virtuale, Fakes può fornire il comportamento predefinito o chiamare il metodo nella classe base. Per effettuare la chiamata al metodo di base, impostare la proprietà CallBase
:
// 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));
Modificare il comportamento predefinito degli stub
Ogni tipo di stub generato contiene un'istanza dell'interfaccia IStubBehavior
tramite la IStub.InstanceBehavior
proprietà . Questo comportamento viene chiamato ogni volta che un client chiama un membro senza delegato personalizzato associato. Se il comportamento non è impostato, usa l'istanza restituita dalla StubsBehaviors.Current
proprietà . Per impostazione predefinita, questa proprietà restituisce un comportamento che genera un'eccezione NotImplementedException
.
È possibile modificare il comportamento in qualsiasi momento impostando la InstanceBehavior
proprietà su qualsiasi istanza di stub. Ad esempio, il frammento di codice seguente modifica il comportamento in modo che lo stub non faccia nulla o restituisca il valore predefinito del tipo default(T)
restituito :
// unit test code
var stub = new StockAnalysis.Fakes.StubIStockFeed();
// return default(T) or do nothing
stub.InstanceBehavior = StubsBehaviors.DefaultValue;
Il comportamento può anche essere modificato a livello globale per tutti gli oggetti stub in cui il comportamento non è impostato con la StubsBehaviors.Current
proprietà :
// Change default behavior for all stub instances where the behavior has not been set.
StubBehaviors.Current = BehavedBehaviors.DefaultValue;