Frage

Wenn ich die Schnittstelle IFoo habe und mehrere Klassen habe, die sie implementieren, was ist dann der beste/eleganteste/klügste Weg, alle diese Klassen anhand der Schnittstelle zu testen?

Ich möchte die Duplizierung von Testcode reduzieren, aber dennoch den Prinzipien des Unit-Tests treu bleiben.

Was würden Sie als Best Practice bezeichnen?Ich verwende NUnit, gehe aber davon aus, dass Beispiele aus jedem Unit-Test-Framework gültig wären

War es hilfreich?

Lösung

Wenn Klassen eine beliebige Schnittstelle implementieren, müssen alle die Methoden in dieser Schnittstelle implementieren.Um diese Klassen zu testen, müssen Sie für jede der Klassen eine Unit-Test-Klasse erstellen.

Gehen wir stattdessen eine intelligentere Route.wenn es Ihr Ziel ist Vermeiden Sie die Duplizierung von Code und Testcode Vielleicht möchten Sie stattdessen eine abstrakte Klasse erstellen, die das verarbeitet wiederkehrend Code.

Z.B.Du hast folgende Schnittstelle:

public interface IFoo {

    public void CommonCode();

    public void SpecificCode();

}

Möglicherweise möchten Sie eine abstrakte Klasse erstellen:

public abstract class AbstractFoo : IFoo {

    public void CommonCode() {
          SpecificCode();
    }

    public abstract void SpecificCode();

}

Das zu testen ist einfach;Implementieren Sie die abstrakte Klasse in der Testklasse entweder als innere Klasse:

[TestFixture]
public void TestClass {

    private class TestFoo : AbstractFoo {
        boolean hasCalledSpecificCode = false;
        public void SpecificCode() {
            hasCalledSpecificCode = true;
        }
    }

    [Test]
    public void testCommonCallsSpecificCode() {
        TestFoo fooFighter = new TestFoo();
        fooFighter.CommonCode();
        Assert.That(fooFighter.hasCalledSpecificCode, Is.True());
    }
}

...oder lassen Sie die Testklasse die abstrakte Klasse selbst erweitern, wenn Ihnen das passt.

[TestFixture]
public void TestClass : AbstractFoo {

    boolean hasCalledSpecificCode;
    public void specificCode() {
        hasCalledSpecificCode = true;
    }

    [Test]
    public void testCommonCallsSpecificCode() {
        AbstractFoo fooFighter = this;
        hasCalledSpecificCode = false;
        fooFighter.CommonCode();
        Assert.That(fooFighter.hasCalledSpecificCode, Is.True());
    }        

}

Wenn sich eine abstrakte Klasse um den allgemeinen Code kümmert, den eine Schnittstelle impliziert, ergibt sich ein viel saubereres Codedesign.

Ich hoffe, das macht für Sie Sinn.


Nebenbei bemerkt, dies ist ein gängiges Designmuster namens Muster der Vorlagenmethode.Im obigen Beispiel ist die Vorlagenmethode die CommonCode Methode und SpecificCode wird Stub oder Hook genannt.Die Idee ist, dass jeder sein Verhalten erweitern kann, ohne die Hintergründe zu kennen.

Viele Frameworks basieren auf diesem Verhaltensmuster, z. ASP.NET wo Sie die Hooks in einer Seite oder einem Benutzersteuerelement wie dem generierten implementieren müssen Page_Load Methode, die von aufgerufen wird Load Bei diesem Ereignis ruft die Template-Methode die Hooks hinter den Kulissen auf.Dafür gibt es noch viele weitere Beispiele.Grundsätzlich wird alles, was Sie implementieren müssen und das die Wörter „load“, „init“ oder „render“ verwendet, von einer Vorlagenmethode aufgerufen.

Andere Tipps

ich stimme nicht überein mit Jon Limjap wenn er sagt,

Es handelt sich nicht um einen Vertrag darüber, a.) wie die Methode implementiert werden soll und b.) was diese Methode genau tun soll (sie garantiert nur den Rückgabetyp), die beiden Gründe, die ich herausgefunden habe, wären Ihr Motiv, diese Art zu wollen des Tests.

Möglicherweise sind viele Teile des Vertrags nicht im Rückgabetyp angegeben.Ein sprachunabhängiges Beispiel:

public interface List {

  // adds o and returns the list
  public List add(Object o);

  // removed the first occurrence of o and returns the list
  public List remove(Object o);

}

Ihre Komponententests für LinkedList, ArrayList, CircularlyLinkedList und alle anderen sollten nicht nur testen, ob die Listen selbst zurückgegeben werden, sondern auch, dass sie ordnungsgemäß geändert wurden.

Da war ein frühere Frage zum Thema „Design-by-Contract“, das Ihnen dabei helfen kann, die richtige Richtung für die Durchführung dieser Tests einzuschlagen.

Wenn Sie den Mehraufwand an Verträgen nicht haben möchten, empfehle ich Ihnen Teststände, in dem Sinne, was Spoke empfohlen:

abstract class BaseListTest {

  abstract public List newListInstance();

  public void testAddToList() {
    // do some adding tests
  }

  public void testRemoveFromList() {
    // do some removing tests
  }

}

class ArrayListTest < BaseListTest {
  List newListInstance() { new ArrayList(); }

  public void arrayListSpecificTest1() {
    // test something about ArrayLists beyond the List requirements
  }
}

Ich glaube nicht, dass dies die beste Vorgehensweise ist.

Die einfache Wahrheit ist, dass eine Schnittstelle nichts anderes als ein Vertrag zur Implementierung einer Methode ist.Es ist nicht einen Vertrag darüber, a.) wie die Methode implementiert werden soll und b.) was diese Methode genau tun soll (sie garantiert nur den Rückgabetyp), die beiden Gründe, die ich herausgefunden habe, wären Ihr Motiv, diese Art von Test zu wollen.

Wenn Sie wirklich die Kontrolle über Ihre Methodenimplementierung haben möchten, haben Sie folgende Möglichkeiten:

  • Implementieren Sie es als Methode in einer abstrakten Klasse und erben Sie von dieser.Sie müssen es immer noch in eine konkrete Klasse vererben, aber Sie sind sicher, dass diese Methode das Richtige tun wird, sofern sie nicht explizit überschrieben wird.
  • In .NET 3.5/C# 3.0 wird die Methode als Erweiterungsmethode implementiert, die auf die Schnittstelle verweist

Beispiel:

public static ReturnType MethodName (this IMyinterface myImplementation, SomeObject someParameter)
{
    //method body goes here
}

Jede Implementierung, die ordnungsgemäß auf diese Erweiterungsmethode verweist, gibt genau diese Erweiterungsmethode aus, sodass Sie sie nur einmal testen müssen.

@Kaiser XLII

Ich mag den Klang kombinatorischer Tests in MbUnit, ich habe die abstrakte Testtechnik für Basisklassenschnittstellen mit NUnit ausprobiert, und obwohl sie funktioniert, müsste man für jede Schnittstelle, die eine Klasse implementiert, eine separate Testvorrichtung haben (da in C# es gibt keine Mehrfachvererbung – obwohl innere Klassen verwendet werden können, was ziemlich cool ist).In Wirklichkeit ist das in Ordnung, vielleicht sogar von Vorteil, weil es Ihre Tests für die implementierende Klasse nach Schnittstelle gruppiert.Aber es wäre gut, wenn das Framework intelligenter sein könnte.Wenn ich ein Attribut verwenden könnte, um eine Klasse als „offizielle“ Testklasse für eine Schnittstelle zu markieren, würde das Framework die zu testende Assembly nach allen Klassen durchsuchen, die die Schnittstelle implementieren, und diese Tests darauf ausführen.

Das wäre cool.

Wie wäre es mit einer Hierarchie der Klassen von [TestFixture]?Fügen Sie den allgemeinen Testcode in die Basistestklasse ein und vererben Sie ihn an untergeordnete Testklassen.

Beim Testen einer Schnittstelle oder eines Basisklassenvertrags lasse ich das Test-Framework lieber automatisch alle Implementierer finden.Dadurch können Sie sich auf die zu testende Schnittstelle konzentrieren und einigermaßen sicher sein, dass alle Implementierungen getestet werden, ohne viel manuelle Implementierung durchführen zu müssen.

  • Für xUnit.net, ich habe eine erstellt Geben Sie Resolver ein Bibliothek, um nach allen Implementierungen eines bestimmten Typs zu suchen (die xUnit.net-Erweiterungen sind nur ein dünner Wrapper über der Type Resolver-Funktionalität, sodass sie für die Verwendung in anderen Frameworks angepasst werden kann).
  • In MbUnit, können Sie a verwenden CombinatorialTest mit UsingImplementations Attribute für die Parameter.
  • Für andere Frameworks das Basisklassenmuster Spoke erwähnt kann nützlich sein.

Über das Testen der Grundlagen der Schnittstelle hinaus sollten Sie auch testen, ob jede einzelne Implementierung ihren besonderen Anforderungen entspricht.

Ich verwende NUnit nicht, habe aber C++-Schnittstellen getestet.Ich würde zuerst eine TestFoo-Klasse testen, die eine grundlegende Implementierung davon ist, um sicherzustellen, dass das generische Zeug funktioniert.Dann müssen Sie nur noch die Besonderheiten jeder Schnittstelle testen.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top