Utilizzo di shim per isolare l'applicazione dagli altri assembly per gli unit test
Tipi di shim è una delle due tecnologie utilizzate dal Framework di falsificazioni Microsoft e facilmente isolare componenti nel test dell'ambiente.I pesi deviano le chiamate ai metodi specifici al codice scritto come parte del test.Molti metodi restituiscono risultati diversi dipendenti dalle condizioni esterne, ma un peso è sotto il controllo del test e restituire i risultati coerenti a ogni chiamata.Ciò rende i test molto più facilmente scrivere.
Utilizzare i pesi per isolare il codice da assembly che non fanno parte della soluzione.Per isolare componenti della soluzione l'uno da, è consigliabile utilizzare stub.
Per un'istruzione di avvio rapido e introduttivo, vedere Isolamento del codice sottoposto a test con Microsoft Fakes
Requisiti
- Visual Studio Ultimate
In questo argomento
Ecco cosa imparerai in questo argomento:
Esempio: Il bug dell'anno 2000
Aggiungere gli assembly di falsificazioni
Utilizzare ShimsContext
Scrivere i test con i pesi
Modificare il comportamento predefinito
Rilevare gli accessi dell'ambiente
Chiamare il metodo originale dal metodo dello shim
Esempio: Il bug dell'anno 2000
Si consideri un metodo che genera un'eccezione il 1° gennaio del 2000:
// code under test
public static class Y2KChecker {
public static void Check() {
if (DateTime.Now == new DateTime(2000, 1, 1))
throw new ApplicationException("y2kbug!");
}
}
Testare questo metodo è particolarmente problematico perché il programma dipende da DateTime.Now, un metodo che dipende dall'orologio del computer, un metodo dipendente dall'ambiente e non deterministico.Inoltre, DateTime.Now è una proprietà statica quindi un tipo stub non può essere utilizzabile qui.Questo problema è sintomo della questione dell'isolamento negli unit test: i programmi che chiamano direttamente le API del database, comunicano con i servizi web, e sono difficili da testare in unità perché la relativa logica dipende dall'ambiente.
Qui è dove i tipi shim devono essere utilizzati.I tipi Shim forniscono un meccanismo per deviare qualsiasi metodo .NET ad un delegato definito dall'utente.I tipi Shim vengono generati dal generatore di falsificazioni e utilizzano i delegati, che chiameranno tipi Shim, per specificare le nuove implementazioni del metodo.
Il test seguente mostra come utilizzare il tipo shim, ShimDateTime, per fornire un'implementazione personalizzata di 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();
}
Come utilizzare i pesi
Aggiungere gli assembly di falsificazioni
In Esplora soluzioni, espandere Riferimentidel progetto di unit test.
- Se si utilizza Visual Basic, è necessario selezionare Mostra tutti i file nella barra degli strumenti di Esplora soluzioni, per visualizzare l'elenco.
Selezionare l'assembly contenente le definizioni di classi per il quale si desidera creare i pesi.Ad esempio, se si desidera rendere il tipo DateTime una zeppa, selezionare System.dll
Scegliere dal menu di scelta rapida, scegliere Aggiungi assembly Fakes.
Utilizzare ShimsContext
Nell'utilizzo dei tipi shim in un framework di unit test, è necessario eseguire il wrapping del codice in un ShimsContext per controllare la durata degli shim.Se non lo richiedessimo, gli shim durerebbero fino al completamento dell'AppDomain.Il modo più semplice per creare uno ShimsContext è tramite l'utilizzo del metodo statico Create() come illustrato nel codice seguente:
//unit test code
[Test]
public void Y2kCheckerTest() {
using(ShimsContext.Create()) {
...
} // clear all shims
}
È necessario eliminare correttamente ogni contesto dello shim.In generale, chiamare sempre ShimsContext.Create all'interno di un'istruzione using per garantire la rimozione appropriata degli shim registrati.Ad esempio, è possibile registrare uno shim per un metodo di test che sostituisce il metodo DateTime.Now con un delegato che restituisce sempre il primo Gennaio del 2000.Se si dimentica di rimuovere lo shim registrato nel metodo di test, il resto dell'esecuzione del test restituisce sempre il primo Gennaio del 2000 come valore di DateTime.Now.Questa situazione può essere sorprendente e poco chiara.
Scrittura di un test con i pesi
Nel codice di test, inserire una deviazione per il metodo da simulare.Ad esempio:
[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
I nomi delle classi dello shim sono costituiti Fakes.Shim l'ambiguità del nome di tipo originale.
I pesi sono effettivamente detour nel codice dell'applicazione sottoposta a test.Ovunque sia una chiamata al metodo originale caso, le falsificazioni che il sistema esegue una deviazione, in modo che anziché chiamare il metodo reale, il codice dello shim viene chiamato.
Si noti che le deviazioni create ed eliminati in fase di esecuzione.È necessario creare sempre deviazione la durata di ShimsContext.Quando si elimina, i pesi creato mentre era attiva viene rimossa.Il modo migliore per eseguire questa operazione consiste in un'istruzione di using.
Può verificarsi un errore di compilazione che informa che lo spazio dei nomi di falsificazioni non esiste.L'errore talvolta viene visualizzato quando sono presenti altri errori di compilazione.Correggere gli errori esterni e il mouse.
Fattori per i tipi di metodi
I tipi di shim consentono di sostituire qualsiasi metodo.NET, compresi i metodi statici o metodi non virtuali, con i propri delegati.
Metodi statici
Le proprietà per associare gli shim a metodi statici vengono inserite in un tipo shim.Ogni proprietà dispone solo di una funzione Set che può essere utilizzata per associare un delegato al metodo di destinazione.Ad esempio, data una classe MyClass con un metodo statico MyMethod:
//code under test
public static class MyClass {
public static int MyMethod() {
...
}
}
È possibile associare uno shim a MyMethod che restituisce sempre 5:
// unit test code
ShimMyClass.MyMethod = () =>5;
Metodi di istanza (per tutte le istanze)
Analogamente ai metodi statici, i metodi di istanza possono essere suddivisi per tutte le istanze.Le proprietà per associare tali shim sono inserite in un tipo annidato denominato AllInstances per evitare confusione.Ad esempio, data una classe MyClass con un metodo di istanza MyMethod:
// code under test
public class MyClass {
public int MyMethod() {
...
}
}
È possibile associare un peso a MyMethod che restituisce sempre 5, indipendentemente dall'istanza:
// unit test code
ShimMyClass.AllInstances.MyMethod = () => 5;
La struttura generata per il tipo di ShimMyClass è simile al seguente codice:
// Fakes generated code
public class ShimMyClass : ShimBase<MyClass> {
public static class AllInstances {
public static Func<MyClass, int>MyMethod {
set {
...
}
}
}
}
Si noti che Fakes passa l'istanza in esecuzione come primo argomento del delegato in questo caso.
Metodi di istanza (per un'istanza runtime)
I metodi di istanza possono essere resi shim da delegati diversi, in base al ricevitore della chiamata.Questo abilita lo stesso metodo di istanza ad avere comportamenti diversi per un'istanza del tipo.Le proprietà per impostare tali shim sono metodi di istanza del tipo shim stesso.Ogni tipo shim instanziato viene associato anche ad un'istanza non elaborata di un tipo shim.
Ad esempio, data una classe MyClass con un metodo di istanza MyMethod:
// code under test
public class MyClass {
public int MyMethod() {
...
}
}
È possibile installare due tipi shim di MyMethod in modo che il primo restituisca sempre 5 e il secondo restituisca sempre 10:
// unit test code
var myClass1 = new ShimMyClass()
{
MyMethod = () => 5
};
var myClass2 = new ShimMyClass { MyMethod = () => 10 };
La struttura generata per il tipo di ShimMyClass è simile al seguente codice:
// Fakes generated code
public class ShimMyClass : ShimBase<MyClass> {
public Func<int> MyMethod {
set {
...
}
}
public MyClass Instance {
get {
...
}
}
}
L'istanza effettiva di tipo shim può essere acceduta tramite la proprietà Instance di:
// unit test code
var shim = new ShimMyClass();
var instance = shim.Instance;
Il tipo shim dispone inoltre di una conversione implicita al tipo shimmed, pertanto è possibile utilizzare in genere semplicemente il tipo shim come è:
// unit test code
var shim = new ShimMyClass();
MyClass instance = shim; // implicit cast retrieves the runtime
// instance
Costruttori
I costruttori possono essere resi shim per collegare i tipi shim agli oggetti futuri.Ogni costruttore viene esposto come un costruttore al metodo statico del tipo shim.Ad esempio, data una classe MyClass con un costruttore che accetta un Integer:
// code under test
public class MyClass {
public MyClass(int value) {
this.Value = value;
}
...
}
Installiamo il tipo shim del costruttore in modo che ogni istanza futura restituisca -5 quando il valore viene richiamato, indipendentemente dal valore nel costruttore:
// unit test code
ShimMyClass.ConstructorInt32 = (@this, value) => {
var shim = new ShimMyClass(@this) {
ValueGet = () => -5
};
};
Si noti che ogni tipo di shim espone due costruttori.Il costruttore predefinito deve essere utilizzato quando è necessaria un'istanza aggiornata, mentre il costruttore che accetta un'istanza shimmed come argomento deve essere utilizzato solo nel costruttore di shim:
// unit test code
public ShimMyClass() { }
public ShimMyClass(MyClass instance) : base(instance) { }
La struttura generata per il tipo di ShimMyClass è simile al codice seguente:
// Fakes generated code
public class ShimMyClass : ShimBase<MyClass>
{
public static Action<MyClass, int> ConstructorInt32 {
set {
...
}
}
public ShimMyClass() { }
public ShimMyClass(MyClass instance) : base(instance) { }
...
}
Membri di base
Le proprietà dello shim dei membri di base possono essere accedute creando uno shim per il tipo di base e passando l'istanza del figlio come parametro al costruttore della classe shim di base.
Ad esempio, data una classe MyBase con un metodo di istanza MyMethod e un sottotipo MyChild:
public abstract class MyBase {
public int MyMethod() {
...
}
}
public class MyChild : MyBase {
}
È possibile installare uno shim di MyBase creando un nuovo shim da ShimMyBase :
// unit test code
var child = new ShimMyChild();
new ShimMyBase(child) { MyMethod = () => 5 };
Si noti che il tipo shim figlio viene convertito in modo implicito all'istanza del figlio una volta passato come parametro al costruttore di base dello shim.
La struttura generata per il tipo di ShimMyChild e di ShimMyBase è simile al seguente codice:
// 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 { ... } }
}
Costruttori statici
I tipi shim espongono un metodo statico StaticConstructor per rendere shim il costruttore di un tipo.Poiché i costruttori statici vengono eseguiti una sola volta, è necessario assicurarsi che lo shim sia configurato prima di accedere a qualsiasi membro del tipo.
Finalizzatori
I finalizzatori non sono supportati in Fakes.
Metodi privati
Il generatore di codice Fakes creerà le proprietà dello shim per i metodi privati che hanno solo tipi visibili nella segnatura, e.s. i tipi di parametri e il tipo restituito visibile.
Interfacce di associazione
Quando un tipo reso shim implementa un'interfaccia, il generatore di codice genera un metodo che consente di associare tutti i membri da quell'interfaccia immediatamente.
Ad esempio, data una classe MyClass che implementa IEnumerable<int>:
public class MyClass : IEnumerable<int> {
public IEnumerator<int> GetEnumerator() {
...
}
...
}
È possibile rendere shim le implementazioni di IEnumerable<int> in MyClass chiamando il metodo di associazione:
// unit test code
var shimMyClass = new ShimMyClass();
shimMyClass.Bind(new List<int> { 1, 2, 3 });
La struttura generata per il tipo di ShimMyClass è simile al codice seguente:
// Fakes generated code
public class ShimMyClass : ShimBase<MyClass> {
public ShimMyClass Bind(IEnumerable<int> target) {
...
}
}
Modificare il comportamento predefinito
Ogni tipo shim generato possiede un'istanza dell'interfaccia IShimBehavior, mediante la proprietà ShimBase<T>.InstanceBehavior.Il comportamento viene utilizzato ogni volta che un client chiama un membro di istanza non esplicitamente reso shim.
Se il comportamento non è stato impostato in modo esplicito, utilizzerà l'istanza restituita dalla proprietà statica ShimsBehaviors.Current.Per impostazione predefinita, questa proprietà restituisce un comportamento che genera un'eccezione NotImplementedException.
Questo comportamento può essere modificato in qualsiasi momento impostando la proprietà InstanceBehavior su qualsiasi istanza dello shim.Ad esempio, il seguente frammento modifica lo shim con un comportamento che non esegue alcuna operazione o restituisce il valore predefinito del tipo di ritorno, ovvero l'impostazione predefinita (T):
// unit test code
var shim = new ShimMyClass();
//return default(T) or do nothing
shim.InstanceBehavior = ShimsBehaviors.DefaultValue;
Il comportamento può anche essere modificato a livello globale per tutte le istanze rese shim per cui la proprietà InstanceBehavior non è stata impostata in modo esplicito settando la proprietà statica ShimsBehaviors.Current :
// unit test code
// change default shim for all shim instances
// where the behavior has not been set
ShimsBehaviors.Current =
ShimsBehaviors.DefaultValue;
Rilevare gli accessi dell'ambiente
È possibile associare un comportamento a tutti i membri, compresi i metodi statici, di un tipo specifico assegnando il comportamento ShimsBehaviors.NotImplemented alla proprietà statica Behavior del tipo shim corrispondente:
// unit test code
// assigning the not implemented behavior
ShimMyClass.Behavior = ShimsBehaviors.NotImplemented;
// shorthand
ShimMyClass.BehaveAsNotImplemented();
Concorrenza
I tipi shim si applicano a tutti i thread nell'AppDomain e non presentano affinità di thread.Si tratta di un fatto importante se si intende utilizzare un test runner che supporta la concorrenza: i test che includono i tipi shim non possono essere eseguiti contemporaneamente.Questa proprietà non è forzata dal runtime di Fakes.
Chiamare il metodo originale dal metodo dello shim
Immagini che si è effettivamente desiderato di scrivere del testo nel file system dopo aver convalidato il nome del file passato al metodo.In tal caso, desidereremmo chiamare il metodo originale nel mezzo del metodo shim.
Il primo approccio per risolvere questo problema consiste nel racchiudere una chiamata al metodo originale tramite un delegato e ShimsContext.ExecuteWithoutShims() come nel codice seguente:
// unit test code
ShimFile.WriteAllTextStringString = (fileName, content) => {
ShimsContext.ExecuteWithoutShims(() => {
Console.WriteLine("enter");
File.WriteAllText(fileName, content);
Console.WriteLine("leave");
});
};
Un altro approccio consiste nell'impostare lo shim a null, chiamare il metodo originale e ripristinare lo shim.
// 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;
Limiti
Gli shim non possono essere utilizzati con tutti i tipi della libreria di classi di base .NET mscorlib e System.
Risorse esterne
Linee guida
Vedere anche
Concetti
Isolamento del codice sottoposto a test con Microsoft Fakes