2012-09-24 5 views
5

Angenommen, die folgende Vorlage Methode Umsetzung:Unit-Test der Template-Methode Design-Muster

public abstract class Abs 
    { 
    void DoYourThing() 
    { 
     log.Info("Starting"); 
     try 
     { 
     DoYourThingInternal(); 
     log.Info("All done"); 
     } 
     catch (MyException ex) 
     { 
     log.Error("Something went wrong here!"); 
     throw; 
     } 
    } 

    protected abstract void DoYourThingInternal(); 

    } 

Nun, es gibt viele Informationen sind rund, wie die Abs Klasse zu testen, und stellen Sie sicher, dass DoYourThingInternal genannt wird.
Allerdings nehme ich möchte, dass meine Conc Klasse testen:

public class Conc : Abs 
    { 
    protected override void DoYourThingInternal() 
    { 
     // Lots of awesome stuff here! 
    } 
    } 

Ich würde nicht conc.DoYourThing() tun wollen, da dies die übergeordnete Methode aufruft, die bereits separat getestet wurde.

Ich möchte nur die übergeordnete Methode testen.

Irgendwelche Ideen?

Antwort

2

Ich würde nicht DoYourThingInternal() von DoYourThing() getrennt zu sein (wie in zwei separate Module des Codes, die isoliert getestet werden können), da Sie nicht in der Lage sein werden, Ihre abstrakte Klasse sowieso sowieso und die 2 Methoden instanziieren immer zusammen laufen. Außerdem hat DoYourThingInternal() Zugriff auf alle geschützten Mitglieder Ihrer Klasse und könnte sie mit möglichen Nebenwirkungen auf DoYourThing() ändern. Also ich denke, es wäre gefährlich, DoYourThing() in völliger Isolation von einer konkreten Implementierung von DoYourThingInternal() zu testen. Das bedeutet jedoch nicht, dass Sie keine separaten Tests für das erwartete Verhalten DoYourThing() haben können, das über alle Implementierungen von Abs und das erwartete Verhalten von DoYourThingInternal() gleich bleiben muss.

Sie können eine (abstrakte) Basistestklasse verwenden, in der Sie einen Test für das erwartete allgemeine Verhalten von DoYourThing() definieren. Erstellen Sie dann so viele Testunterklassen, wie es Implementierungen von Abs gibt, mit Komponententests für die Besonderheiten jeder Implementierung.

Der Test von der Basistestklasse wird vererbt, und wenn Sie jede Unterklasse der Tests ausführen, wird die geerbte Test für DoYourThing() auch laufen:

public abstract class AbsBaseTest 
{ 
    public abstract Abs GetAbs(); 

    [Test] 
    public void TestSharedBehavior() 
    { 
    getAbs().DoYourThing(); 

    // Test shared behavior here... 
    } 
} 

[TestFixture] 
public class AbsImplTest : AbsBaseTest 
{ 
    public override Abs GetAbs() 
    { 
    return new AbsImpl(); 
    } 

    [Test] 
    public void TestParticularBehavior() 
    { 
    getAbs().DoYourThing(); 

    // Test specific behavior here 
    } 
} 

Siehe http://hotgazpacho.org/2010/09/testing-pattern-factory-method-on-base-test-class/

Sie wissen nicht, wenn Die abstrakte Testklassenvererbung wird jedoch von allen Unit-Test-Frameworks unterstützt (ich glaube, NUnit tut dies).

0

Wie wäre es, ein Interface auf Abs zu kleben und es zu verspotten? Die Anrufe ignorieren oder Erwartungen an sie setzen?

+0

nicht sicher ich verstehe; Könntest du es ausarbeiten? –

3

Ich vermute, ein Teil des "Problems" ist, dass es keine Möglichkeit gibt, eine geschützte Methode von außerhalb der Klasse aufzurufen. Wie wäre es ein Mock-Klasse, die von Conc ableitet und bietet eine neue öffentliche Methode:

public class MockConc: Conc 
    { 
    void MockYourThingInternal() 
    { 
     DoYourThingInternal() 
    } 
    } 
2

Sie haben die Frage „TDD“ bezeichnet, aber ich bezweifle, dass Sie habe gefolgt, dass Prinzip, wenn Sie dieses „Problem“ anzutreffen.

Wenn Sie für einige Logik gewesen TDD Ihre Arbeitsabläufe wäre so etwas wie

  1. einen Test schreiben noch nicht implementiert
  2. Impl die einfachste impl für diesen Test zu machen, grün (Logik wirklich gefolgt auf Konz1 zum Beispiel)
  3. Umgestalten
  4. einen Test für eine andere Logik schreiben noch nicht implementiert
  5. Impl die einfachste impl für diesen Test auf CONC2 für e es grün (Logik zu machen xample)
  6. Umgestalten

In „6“, könnte man denken, dass es eine gute Idee wäre, eine Template-Methode zu implementieren, da Konz1 und CONC2 gewisse Logik teilen. Tun Sie es einfach und führen Sie Ihre Tests durch, um zu sehen, dass die Logik immer noch funktioniert.

Schreiben Sie Tests, um die Logik zu überprüfen, nicht Basis wie die Implementierung aussehen (= mit den Tests beginnen). Beginnen Sie in diesem Fall mit dem Schreiben von Tests, die bestätigen, dass die Logik funktioniert (die Logik, die später in Ihren konkreten Typen platziert wird). Ja, das bedeutet, dass einige Codezeilen (die in Ihrer abstrakten Klasse) mehrfach getestet werden. Na und? Einer der Punkte beim Schreiben von Tests ist, dass Sie in der Lage sein sollten, Ihren Code zu refaktorieren, aber immer noch in der Lage zu sein, zu verifizieren, dass er funktioniert, indem Sie Ihre Tests durchführen. Wenn Sie später nicht das Vorlagenmethodenmuster verwenden möchten, sollten Sie in einer idealen Welt keine Tests ändern müssen, sondern nur die Implementierung ändern.

Wenn Sie anfangen zu denken, welche Codezeilen Sie testen, IMO verlieren Sie viel von den Vorteilen des Schreibens Tests überhaupt. Sie möchten sicherstellen, dass Ihre Logik funktioniert - schreiben Sie stattdessen Tests dafür.

+1

Ich sehe was du meinst. Und es macht Sinn. Wenn ich jedoch, wie Sie vorgeschlagen haben, einige Codezeilen mehrmals teste, bedeutet dies, dass ich, wenn ich diese Codezeilen ändere, mehrere Tests ändern muss. Und das könnte ein hoher Preis sein ... –

+3

(vorausgesetzt, wir reden jetzt TDD ...) Sie ändern nicht nur Code-Zeilen. Sie fügen neue Funktionen hinzu, dh Sie fügen einen oder mehrere neue Tests hinzu. Um diese zu bestehen, ändern Sie den implementierten Code. Wenn das bedeutet, dass alte Tests abgebrochen werden, bedeutet dies, dass einige alte Spezifikationen/Tests nicht mehr gültig sind. Und das ist die gute Sache, Tests zu haben -> Sie haben jetzt eine Chance zu wissen, dass Ihr neuer Code altes Verhalten verändert hat. Hat der neue Code alten Code gebrochen? Sind die alten Tests/Spezifikationen nicht mehr gültig? Müssen Sie Ihren Kunden/Produktmanager fragen "Was ist mit der alten Spezifikation? Nicht mehr gültig?" – Roger

+1

Sehr guter Punkt. zugegebenermaßen fällt es mir schwer, mit einer "TDD" -Mentalität zu denken ... –

0

Sie könnten es auf mehrere Arten tun, von denen viele hier bereits dokumentiert sind. Hier ist der Ansatz, den ich normalerweise nehme: Lassen Sie den Testfall von der konkreten Klasse erben.

Hoffe, dass hilft!

Brandon