2016-05-16 11 views
3

Ich möchte Komponententests für eine Klasse schreiben, die IDisposable implementiert. Die Klasse verfügt über zahlreiche private Felder, die IDisposable ebenfalls implementieren. In meinem Test möchte ich überprüfen, dass, wenn ich Dispose() aufrufen, es korrekt Dispose() auf alle IDisposable-Felder aufruft. Im Wesentlichen möchte ich, dass mein Komponententest wie folgt aussieht:Wie Unit Test Dispose() mit Reflektion?

var o = new ObjectUnderTest(); 
o.Dispose(); 
Assert.IsFalse(ObjectHasUndisposedDisposables(o)); 

Ich dachte über Reflexion, um dies zu erreichen. Es scheint, als ob dies eine ziemlich häufige Anforderung wäre, aber ich kann keine Beispiele dafür finden.

Wer hat das versucht?

BEARBEITEN - Ich möchte nicht die Einmalartikel in die zu testende Klasse injizieren müssen.

+0

'' o.IsDisposed == true''? in Ihrem Fall wahrscheinlich: '' Assert.IsTrue (o.IsDispoed); '' –

+1

Ich empfehle die Verwendung eines Abhängigkeitsinjektionscontainers. Die Verwaltung von verfügbaren Abhängigkeiten ist nur ein Vorteil. Wenn Ihre Klasse mehr Abhängigkeiten erstellt, die sie verfolgen und entsorgen muss, ist sie nicht von Abstraktionen abhängig. Mit DI hängt deine Klasse nur von "IWeverything" ab. Es weiß nicht, ob diese Klasse wegwerfbar ist. –

Antwort

6

Die einzige Möglichkeit, das Verhalten, das Sie suchen, ohne Refactoring in Ihrem Code zu überprüfen, ist die Verwendung von Code-Web-Tools. Typemock Isolator, MsFakes und etc ...

Der folgende Ausschnitt zeigt die Art und Weise das Verhalten mit MsFakes zu überprüfen:

[TestMethod] 
public void TestMethod1() 
{ 
    var wasCalled = false; 
    using (ShimsContext.Create()) 
    { 
     ForMsFakes.Fakes.ShimDependency.AllInstances.Dispose = dependency => 
     { 
      wasCalled = true; 
     }; 

     var o = new ObjectUnderTest(); 

     o.Dispose(); 
    } 

    Assert.IsTrue(wasCalled); 
} 

public class Dependency : IDisposable 
{ 
    public void Dispose() {} 
} 

public class ObjectUnderTest: IDisposable 
{ 
    private readonly Dependency _d = new Dependency(); 

    public void Dispose() 
    { 
     _d.Dispose(); 
    } 
} 
+0

Ein Nachteil ist, dass es nicht generisch ist. Nach meinem besten Wissen gibt es keine automatische Möglichkeit, während der Laufzeit eine Shim zu generieren, so dass Sie für jeden verfügbaren Typ in der Klasse manuell eine "ShimXxxxxx" generieren müssten. Und wenn die Klasse zu einem späteren Zeitpunkt mit einem neuen Wegwerftyp aktualisiert wird, wird der Komponententest nicht danach suchen. Dies nimmt die Hälfte des Wertes des Komponententests weg, da er Sie nicht schützt, wenn neue Typen hinzugefügt werden. Jedoch gibt es eine Arbeit um ... –

+0

Do 'var fieldCount = typeof (ClassUnderTest) .GetFields (BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic). Wo (x => typeof (IDisposable). IsAssignableFrom (x.FieldType)).Anzahl(); Assert.AreEqual (KnownFieldCount, fieldCount, "Die Anzahl der bekannten Felder stimmt nicht mit der Klasse überein. Aktualisieren Sie den Komponententest nach Bedarf mit neuen Dispose-Klassen"); 'als einer Ihrer Tests. Dies führt dazu, dass der Test fehlschlägt, wenn jemand die Klasse mit einer anderen Anzahl von Disposables aktualisiert, es wird jedoch nicht markiert, wenn Sie beide entfernen und einen hinzufügen. –

5

Es gibt keine generische Möglichkeit, dies zu handhaben. Die Schnittstelle IDisposeable erfordert nicht, dass Sie verfolgen, ob die Entsorgung angerufen wurde oder nicht.

Der einzige Weg, an den ich denken könnte, dass Sie damit umgehen könnten, ist, wenn all diese verfügbaren Klassen injiziert wurden, indem die Abhängigkeit injiziert wurde. Wenn Sie das getan haben, könnten Sie die injizierten Klassen verspotten und verfolgen, ob dispose aufgerufen wurde.

[TestMethod] 
    public void TestAllDisposeablesAreDisposed() 
    { 
     var fooMock = new Mock<IFoo>(); 
     fooMock.Setup(x=>x.Dispose()) 
      .Verifiable("Dispose never called on Foo"); 

     var barMock = new Mock<IBar>(); 
     barMock.Setup(x => x.Dispose()) 
      .Verifiable("Dispose never called on Bar"); 

     using (var myClass = new MyClass(fooMock.Object, barMock.Object)) 
     { 
     } 

     fooMock.Verify(); 
     barMock.Verify(); 
    } 
+0

Dies ist eine gute Antwort. Wenn Sie die Verfügbarkeit Ihrer Abhängigkeiten in der Klasse behandeln, schreiben Sie Tests, um dies zu überprüfen. Sie werden brechen, wenn Sie die Entsorgungslogik außerhalb der Klasse verschieben, was eine gute Sache ist. –

+0

Es macht mir auch nichts aus, alle Einmalartikel, die ich verwende, in eine andere Schnittstelle zu verpacken (sagen wir ITrackedDisposable), die nachverfolgt wird, wenn sie entsorgt wurden. – user6118784

2

Sind Sie, ob Ihr IDisposable Verhalten innerhalb einer bestimmten Klasse arbeitet zu testen, versuchen, oder tun Sie testen möchten, ob das, was auch immer ist die Verwaltung Ihrer Wegwerf-Klasse IDisposable tatsächlich ruft? Das ist gültig, wenn Sie nicht sicher sind, dass es angerufen wird und Sie sicher sein wollen. (Ich habe für die Tests geschrieben.)

Für, dass Sie eine einfache Klasse wie folgt erstellen:

public class DisposeMe : IDisposable 
{ 
    public bool Disposed {get;set;} 
    public void Dispose() 
    { 
     Disposed = true; 
    } 
} 

Dann können Sie einfach behaupten, dass Disposed wahr ist, wenn Sie es erwarten angeordnet worden zu sein.

Zum Beispiel habe ich das verwendet, wenn ich Klassen aus einem DI-Container erstellen, und ich möchte bestätigen, dass wenn eine Klasse durch den Container freigegeben wird, seine verfügbaren Abhängigkeiten entsorgt werden. Ich kann das nicht in den konkreten Klassen nachverfolgen, aber ich kann testen, dass der DI-Container tut, was er meiner Meinung nach tun sollte. (Das ist kein Test, den ich wieder und wieder schreiben würde. Sobald ich das erwartete Verhalten von Windsor zum Beispiel bestätigt habe, werde ich keine Unit-Tests mehr schreiben.)

+0

Ich möchte das testen, wenn ich ObjectUnderTest.Dispose() aufrufen, ruft es Dispose() für alle seine konfigurierten Disposables korrekt auf. Mir ist klar, dass ich das machen kann, indem ich die Verbrauchsmaterialien injiziere, aber ich denke, das ist ein schlechtes Design, weil sich meine Schnittstelle aufgrund einer Änderung der Implementierung ändern muss. Es scheint, dass die Reflexion ein viel saubererer Weg wäre, dies zu erreichen, und sie könnte immer wieder auf jedem IDisposable wiederverwendet werden. – user6118784

+0

@ user6118784 Reflexion von was? Jedes Mal, wenn du Reflektionen benutzen willst, ersetze es durch "mache alle Felder öffentlich", so wird dein Satz zu "Es scheint, als ob * alle Felder öffentlich gemacht würden * wäre ein viel saubererer Weg, dies zu erreichen, und könnte wieder und wieder verwendet werden IDisposable ". Wie würde das Lösen der Felder Ihr Problem lösen, wenn jemand auf einem dieser Felder ".Dispose()" anruft? Sie müssen den Anruf abfangen, um ihn erkennen zu können, und ohne in wirklich exotische Sachen wie IL-Umschreiben gehen zu müssen, müssen Sie eine Injektion verwenden, um das zu tun. –

+0

Um Ihre Fragen zu beantworten: 1) Reflexion der privaten Felder von ObjectUnderTest. 2) Wenn ich die Einwegfelder öffentlich machen würde, könnte mein Komponententest leicht prüfen, ob sie entsorgt wurden - ich würde alle meine Einwegartikel in etwas wie ein ITrackedDisposable (das verfolgt, wenn Dispose() auf ihnen aufgerufen wird) einwickeln Dies. – user6118784

0

Wenn Sie nicht wollen Verwenden Sie Dependency Injection im Konstruktor, da dies Sie zwingen würde, Ihre Schnittstelle zu ändern, dann könnten Sie Ihre Abhängigkeiten über Eigenschaften injizieren. Geben Sie Ihrer Eigenschaft einen Standardwert, wenn nichts injiziert wird, so dass Sie bei der Verwendung der Klasse nicht daran denken müssen, die echte Implementierung zu übergeben, z. B. .

public class ObjectUnderTest : IDisposable 
{ 
    private IObjectNeedingDisposal _foo; 

    public IObjectNeedingDisposal Foo 
    { 
     get { return _foo ?? (_foo = new ObjectNeedingDisposal()); } 
     set { _foo = value; } 
    } 

    public void MethodUsingDisposable() 
    { 
     Foo.DoStuff(); 
    } 

    public void Dispose() 
    { 
     Foo.Dispose(); 
    } 
} 

In Ihren Tests konnte man dann eine Instanz des Objekts erstellen und in der Mock-Version Ihrer Klasse übergeben, aber in der realen Umsetzung könnte man es nicht gesetzt verlassen und es würde Sie wollten auf eine Instanz des Typs Standard

+0

Ja, ich hatte diesen Ansatz ebenfalls in Betracht gezogen, aber ich mag es wirklich nicht, öffentliche Dinge nur zum Zwecke des Komponententests in Klassen zu integrieren. Es scheint klobig und es ist auch Code, der nicht wiederverwendet werden kann. Ich weiß, was ich tun möchte, kann mit Reflexion getan werden - ich hatte gehofft, dass jemand es bereits in einem netten wiederverwendbaren Modul geschrieben hatte, um mich zu sparen :-). – user6118784

+0

Ich stimme zu, dass es nicht perfekt ist, Eigenschaften auf diese Weise zu verwenden, ich bevorzuge es, sie im Konstruktor zu übergeben. Ich weiß, dass Sie Reflektion verwenden können, um private Felder zu erhalten - http://stackoverflow.com/questions/95910/find-a-private-field-with-reflection, aber selbst dann hätten Sie das Ereignis verpasst und könnten es nicht sagen nachdem die Disposed-Methode aufgerufen wurde. –