it-swarm.com.de

Der beste Weg zu Unit-Test-Methoden, die andere Methoden innerhalb derselben Klasse aufrufen

Ich habe kürzlich mit einigen Freunden besprochen, welche der folgenden beiden Methoden am besten geeignet ist, um Rückgabeergebnisse oder Aufrufe von Methoden innerhalb derselben Klasse von Methoden innerhalb derselben Klasse zu stubben.

Dies ist ein sehr vereinfachtes Beispiel. In Wirklichkeit sind die Funktionen viel komplexer.

Beispiel:

public class MyClass
{
     public bool FunctionA()
     {
         return FunctionB() % 2 == 0;
     }

     protected int FunctionB()
     {
         return new Random().Next();
     }
}

Um dies zu testen, haben wir 2 Methoden.

Methode 1: Verwenden Sie Funktionen und Aktionen, um die Funktionalität der Methoden zu ersetzen. Beispiel:

public class MyClass
{
     public Func<int> FunctionB { get; set; }

     public MyClass()
     {
         FunctionB = FunctionBImpl;
     }

     public bool FunctionA()
     {
         return FunctionB() % 2 == 0;
     }

     protected int FunctionBImpl()
     {
         return new Random().Next();
     }
}

[TestClass]
public class MyClassTests
{
    private MyClass _subject;

    [TestInitialize]
    public void Initialize()
    {
        _subject = new MyClass();
    }

    [TestMethod]
    public void FunctionA_WhenNumberIsOdd_ReturnsTrue()
    {
        _subject.FunctionB = () => 1;

        var result = _subject.FunctionA();

        Assert.IsFalse(result);
    }
}

Methode 2: Mitglieder virtuell machen, Klasse ableiten und in abgeleiteten Klassen Funktionen und Aktionen verwenden, um Funktionen zu ersetzen Beispiel:

public class MyClass
{     
     public bool FunctionA()
     {
         return FunctionB() % 2 == 0;
     }

     protected virtual int FunctionB()
     {
         return new Random().Next();
     }
}

public class TestableMyClass
{
     public Func<int> FunctionBFunc { get; set; }

     public MyClass()
     {
         FunctionBFunc = base.FunctionB;
     }

     protected override int FunctionB()
     {
         return FunctionBFunc();
     }
}

[TestClass]
public class MyClassTests
{
    private TestableMyClass _subject;

    [TestInitialize]
    public void Initialize()
    {
        _subject = new TestableMyClass();
    }

    [TestMethod]
    public void FunctionA_WhenNumberIsOdd_ReturnsTrue()
    {
        _subject.FunctionBFunc = () => 1;

        var result = _subject.FunctionA();

        Assert.IsFalse(result);
    }
}

Ich möchte wissen, was besser ist und auch WARUM?

Update: HINWEIS: FunctionB kann auch öffentlich sein

36
tranceru1

Nach dem Original-Poster-Update bearbeitet.

Haftungsausschluss: kein C # -Programmierer (meistens Java oder Ruby). Meine Antwort wäre: Ich würde es überhaupt nicht testen, und ich denke nicht, dass Sie sollten.

Die längere Version lautet: Private/geschützte Methoden sind keine Teile der API, sondern im Grunde genommen Implementierungsoptionen, die Sie überprüfen, aktualisieren oder vollständig wegwerfen können, ohne dass dies Auswirkungen auf die Außenwelt hat.

Ich nehme an, Sie haben einen Test für FunctionA (), den Teil der Klasse, der von der Außenwelt aus sichtbar ist. Es sollte das einzige sein, das einen Vertrag zur Implementierung hat (und der getestet werden könnte). Ihre private/geschützte Methode hat keinen Vertrag zu erfüllen und/oder zu testen.

Eine entsprechende Diskussion finden Sie dort: https://stackoverflow.com/questions/105007/should-i-test-private-methods-or-only-public-ones

Wenn FunctionB nach dem Kommentar öffentlich ist, teste ich einfach beide mit Unit Unit. Sie mögen denken, dass der Test von FunctionA nicht vollständig "Einheit" ist (wie er FunctionB nennt), aber ich würde mir darüber keine Sorgen machen: Wenn der FunctionB-Test funktioniert, aber nicht der FunctionA-Test, bedeutet dies eindeutig, dass das Problem nicht in der liegt Subdomain von FunctionB, was für mich als Diskriminator gut genug ist.

Wenn Sie wirklich in der Lage sein möchten, die beiden Tests vollständig zu trennen, würde ich beim Testen von FunctionA eine Art Verspottungstechnik verwenden, um FunctionB zu verspotten (normalerweise einen festen bekannten korrekten Wert zurückgeben). Mir fehlt das Wissen über das C # -Ökosystem, um eine bestimmte Spottbibliothek zu beraten, aber Sie können sich diese Frage ansehen.

32
Martin

Ich stimme der Theorie zu, dass es wichtig genug ist, eine Funktion zu testen oder zu ersetzen, wenn sie wichtig ist, nicht ein privates Implementierungsdetail der zu testenden Klasse zu sein, sondern ein öffentliches Implementierungsdetail eines anders Klasse.

Also, wenn ich in einem Szenario bin, in dem ich habe

class A 
{
     public B C()
     {
         D();
     }

     private E D();
     {
         // i actually want to control what this produces when I test C()
         // or this is important enough to test on its own
         // and, typically, both of the above
     }
}

Dann werde ich umgestalten.

class A 
{
     ICollaborator collaborator;

     public A(ICollaborator collaborator)
     {
         this.collaborator = collaborator;
     }

     public B C()
     {
         collaborator.D();
     }
}

Jetzt habe ich ein Szenario, in dem D() unabhängig voneinander testbar und vollständig austauschbar ist.

Als Organisationsmittel lebt mein Mitarbeiter möglicherweise nicht auf derselben Namespace-Ebene. Wenn sich beispielsweise A in FooCorp.BLL befindet, ist mein Mitarbeiter möglicherweise eine weitere Ebene tief, wie in FooCorp.BLL.Collaborators (oder einem beliebigen Namen). Mein Mitarbeiter ist möglicherweise nur innerhalb der Assembly über den Zugriffsmodifikator internal sichtbar, den ich dann auch über das Assembly-Attribut InternalsVisibleTo für meine Unit-Test-Projekte verfügbar machen würde. Zum Mitnehmen können Sie Ihre API für Anrufer weiterhin sauber halten und gleichzeitig überprüfbaren Code erstellen.

11
Anthony Pegram

Ich persönlich verwende Methode1, d. H. Alle Methoden in Aktionen oder Funktionen umwandeln, da dies die Testbarkeit von Code für mich erheblich verbessert hat. Wie bei jeder Lösung gibt es bei diesem Ansatz Vor- und Nachteile:

Vorteile

  1. Ermöglicht eine einfache Codestruktur, bei der die Verwendung von Codemustern nur für Unit-Tests zusätzliche Komplexität hinzufügen kann.
  2. Ermöglicht das Versiegeln von Klassen und das Eliminieren virtueller Methoden, die von gängigen Mocking-Frameworks wie Moq benötigt werden. Das Versiegeln von Klassen und das Eliminieren virtueller Methoden machen sie zu Kandidaten für Inlining und andere Compiler-Optimierungen. ( https://msdn.Microsoft.com/en-us/library/ff647802.aspx )
  3. Vereinfacht die Testbarkeit, da das Ersetzen der Func/Action-Implementierung in einem Unit-Test so einfach ist wie das Zuweisen eines neuen Werts zur Func/Action
  4. Ermöglicht auch das Testen, ob eine statische Funktion von einer anderen Methode aufgerufen wurde, da statische Methoden nicht verspottet werden können.
  5. Bestehende Methoden lassen sich leicht in Funktionen/Aktionen umgestalten, da die Syntax zum Aufrufen einer Methode auf der Aufrufsite dieselbe bleibt. (Für Zeiten, in denen Sie eine Methode nicht in eine Funktion/Aktion umgestalten können, siehe Nachteile)

Nachteile

  1. Funktionen/Aktionen können nicht verwendet werden, wenn Ihre Klasse abgeleitet werden kann, da Funktionen/Aktionen keine Vererbungspfade wie Methoden haben
  2. Standardparameter können nicht verwendet werden. Das Erstellen einer Funktion mit Standardparametern erfordert das Erstellen eines neuen Delegaten, wodurch der Code je nach Anwendungsfall verwirrend wird
  3. Die benannte Parametersyntax kann nicht zum Aufrufen von Methoden wie etwas verwendet werden (Vorname: "S", Nachname: "K")
  4. Der größte Nachteil ist, dass Sie in Funcs and Actions keinen Zugriff auf die Referenz 'this' haben. Daher müssen Abhängigkeiten von der Klasse explizit als Parameter übergeben werden. Es ist gut, da Sie alle Abhängigkeiten kennen, aber schlecht, wenn Sie viele Eigenschaften haben, von denen Ihr Func abhängt. Ihr Kilometerstand hängt von Ihrem Anwendungsfall ab.

Zusammenfassend lässt sich sagen, dass die Verwendung von Funktionen und Aktionen für den Komponententest großartig ist, wenn Sie wissen, dass Ihre Klassen niemals überschrieben werden.

Außerdem erstelle ich normalerweise keine Eigenschaften für Funcs, sondern inline sie direkt als solche

public class MyClass
{
     public Func<int> FunctionB = () => new Random().Next();

     public bool FunctionA()
     {
         return FunctionB() % 2 == 0;
     }
}

Hoffe das hilft!

0

Hinzufügen zu dem, worauf Martin hinweist,

Wenn Ihre Methode privat/geschützt ist, testen Sie sie nicht. Es ist klassenintern und sollte nicht außerhalb der Klasse aufgerufen werden.

In beiden von Ihnen erwähnten Ansätzen habe ich folgende Bedenken:

Methode 1 - Dies ändert tatsächlich das Verhalten der zu testenden Klasse im Test.

Methode 2 - Hiermit wird der Produktionscode nicht getestet, sondern eine andere Implementierung.

In dem angegebenen Problem sehe ich, dass die einzige Logik von A darin besteht, zu sehen, ob der Ausgang von FunctionB gerade ist. Obwohl dies veranschaulichend ist, gibt FunctionB einen zufälligen Wert an, der schwer zu testen ist.

Ich erwarte ein realistisches Szenario, in dem wir MyClass so einrichten können, dass wir wissen, was FunctionB zurückgeben würde. Dann ist unser erwartetes Ergebnis bekannt, wir können FunctionA aufrufen und das tatsächliche Ergebnis bestätigen.