it-swarm.com.de

Soll ich geerbte Methoden testen?

Angenommen, ich habe eine Klasse Manager, die von einer Basisklasse Employee abgeleitet ist, und dieser Employee hat eine Methode getEmail () that wird von Manager geerbt. Sollte ich testen, ob das Verhalten der getEmail () Methode eines Managers tatsächlich das gleiche ist wie das eines Mitarbeiters?

Zu dem Zeitpunkt, an dem diese Tests geschrieben werden, wird das Verhalten dasselbe sein, aber natürlich könnte irgendwann in der Zukunft jemand diese Methode überschreiben, ihr Verhalten ändern und daher meine Anwendung beschädigen. Es erscheint jedoch etwas seltsam, im Wesentlichen zu prüfen, ob kein Einmischungscode vorhanden ist.

(Beachten Sie, dass das Testen der Methode Manager :: getEmail () die Codeabdeckung (oder andere Codequalitätsmetriken (?)) Erst verbessert, wenn Manager :: getEmail () wird erstellt/überschrieben.)

(Wenn die Antwort "Ja" lautet, sind einige Informationen zum Verwalten von Tests hilfreich, die von Basisklassen und abgeleiteten Klassen gemeinsam genutzt werden.)

Eine äquivalente Formulierung der Frage:

Wenn eine abgeleitete Klasse eine Methode von einer Basisklasse erbt, wie können Sie ausdrücken (testen), ob Sie von der geerbten Methode Folgendes erwarten:

  1. Verhalten Sie sich genauso wie die Basis im Moment (wenn sich das Verhalten der Basis ändert, ändert sich das Verhalten der abgeleiteten Methode nicht).
  2. Verhalten Sie sich für alle Zeiten genauso wie die Basis (wenn sich das Verhalten der Basisklasse ändert, ändert sich auch das Verhalten der abgeleiteten Klasse). oder
  3. Benimm dich, wie es will (dir ist das Verhalten dieser Methode egal, weil du sie nie aufrufst).
30
mjs

Ich würde hier den pragmatischen Ansatz verfolgen: Wenn in Zukunft jemand Manager :: getMail überschreibt, liegt es in der Verantwortung des Entwicklers , Testcode bereitzustellen für die neue Methode.

Das gilt natürlich nur, wenn Manager::getEmail hat wirklich den gleichen Codepfad wie Employee::getEmail! Auch wenn die Methode nicht überschrieben wird, kann sie sich anders verhalten:

  • Employee::getEmail könnte ein geschütztes virtuelles getInternalEmail aufrufen, das in Manager überschrieben wird.
  • Employee::getEmail könnte auf einen internen Status zugreifen (z. B. auf ein Feld _email), die sich in den beiden Implementierungen unterscheiden können: Beispielsweise könnte die Standardimplementierung von Employee sicherstellen, dass _email ist immer [email protected], aber Manager ist flexibler bei der Zuweisung von E-Mail-Adressen.

In solchen Fällen ist es möglich, dass sich ein Fehler nur in Manager::getEmail, obwohl die Implementierung der Methode selbst dieselbe ist. In diesem Fall wird Manager::getEmail separat könnte Sinn machen.

23
Heinzi

Ich würde.

Wenn Sie denken "gut, es ruft wirklich nur Employee :: getEmail () auf, weil ich es nicht überschreibe, also muss ich Manager :: getEmail () nicht testen", dann testen Sie das Verhalten nicht wirklich von Manager :: getEmail ().

Ich würde mir überlegen, was nur Manager :: getEmail () tun soll, nicht ob es geerbt oder überschrieben wird. Wenn das Verhalten von Manager :: getEmail () darin bestehen soll, das zurückzugeben, was Employee :: getMail () zurückgibt, ist dies der Test. Wenn das Verhalten darin besteht, "[email protected]" zurückzugeben, ist dies der Test. Es spielt keine Rolle, ob es durch Vererbung implementiert oder überschrieben wird.

Was wichtig ist, ist, dass wenn es sich in Zukunft ändert, Ihr Test es fängt und Sie wissen, dass etwas entweder kaputt gegangen ist oder überdacht werden muss.

Einige Leute mögen mit der scheinbaren Redundanz in der Prüfung nicht einverstanden sein, aber mein Gegenpol dazu wäre, dass Sie das Verhalten von Employee :: getMail () und Manager :: getMail () als unterschiedliche Methoden testen, unabhängig davon, ob sie geerbt wurden oder nicht überschrieben. Wenn ein zukünftiger Entwickler das Verhalten von Manager :: getMail () ändern muss, muss er auch die Tests aktualisieren.

Die Meinungen können jedoch variieren, ich denke, Digger und Heinzi haben vernünftige Gründe für das Gegenteil gegeben.

14
seggy

Testen Sie es nicht. Führen Sie einen Funktions-/Abnahmetest durch.

Unit-Tests sollten jede Implementierung testen. Wenn Sie keine neue Implementierung bereitstellen, halten Sie sich an das Prinzip DRY). Wenn Sie hier einige Anstrengungen unternehmen möchten, können Sie den ursprünglichen Unit-Test verbessern. Nur wenn Sie überschreiben die Methode, wenn Sie einen Komponententest schreiben.

Gleichzeitig sollten Funktions-/Abnahmetests sicherstellen, dass Ihr gesamter Code am Ende des Tages das tut, was er soll, und hoffentlich jede Verrücktheit durch Vererbung auffängt.

5
MrFox

Robert Martins Regeln für TDD sind:

  1. Sie dürfen keinen Produktionscode schreiben, es sei denn, Sie müssen einen fehlgeschlagenen Komponententest bestehen.
  2. Sie dürfen nicht mehr von einem Komponententest schreiben, als ausreicht, um zu scheitern. und Kompilierungsfehler sind Fehler.
  3. Sie dürfen nicht mehr Produktionscode schreiben, als ausreicht, um den einen fehlgeschlagenen Komponententest zu bestehen.

Wenn Sie diese Regeln befolgen, können Sie diese Frage nicht stellen. Wenn die getEmail -Methode getestet werden muss, wäre sie getestet worden.

4
Daniel Kaplan

Ja, Sie sollten geerbte Methoden testen, da sie in Zukunft möglicherweise überschrieben werden. Außerdem können geerbte Methoden virtuelle Methoden aufrufen, die überschrieben werden, was das Verhalten der nicht überschriebenen geerbten Methode ändern würde.

Ich teste dies, indem ich eine abstrakte Klasse erstelle, um die (möglicherweise abstrakte) Basisklasse oder Schnittstelle wie folgt zu testen (in C # mit NUnit):

public abstract class EmployeeTests
{
    protected abstract Employee CreateInstance(string name, int age);

    [Test]
    public void GetEmail_ReturnsValidEmailAddress()
    {
        // Given
        var sut = CreateInstance("John Doe", 20);

        // When
        string email = sut.GetEmail();

        // Then
        Assert.IsTrue(Helper.IsValidEmail(email));
    }
}

Dann habe ich eine Klasse mit Tests, die für Manager spezifisch sind, und integriere die Tests des Mitarbeiters wie folgt:

[TestFixture]
public class ManagerTests
{
    // Other tests.

    [TestFixture]
    public class ManagerEmployeeTests : EmployeeTests
    {
        protected override Employee CreateInstance(string name, int age);
        {
            return new Manager(name, age);
        }
    }
}

Der Grund, warum ich das kann, ist Liskovs Substitutionsprinzip: Die Tests von Employee sollten immer noch bestehen, wenn ein Manager Objekt übergeben wird, da es von Employee abgeleitet ist. Daher muss ich meine Tests nur einmal schreiben und kann überprüfen, ob sie für alle möglichen Implementierungen der Schnittstelle oder Basisklasse funktionieren.

Sollte ich testen, ob das Verhalten der getEmail () -Methode eines Managers tatsächlich das gleiche ist wie das eines Mitarbeiters?

Ich würde nein sagen, da es meiner Meinung nach ein wiederholter Test wäre. Ich würde einmal in den Mitarbeitertests testen und das wäre es.

Zum Zeitpunkt des Schreibens dieser Tests ist das Verhalten dasselbe, aber natürlich könnte irgendwann in der Zukunft jemand diese Methode überschreiben und ihr Verhalten ändern

Wenn die Methode überschrieben wird, benötigen Sie neue Tests, um das überschriebene Verhalten zu überprüfen. Dies ist die Aufgabe der Person, die die überschreibende Methode getEmail() implementiert.

2
user18041

Nein, Sie müssen geerbte Methoden nicht testen. Klassen und ihre Testfälle, die auf dieser Methode basieren, werden ohnehin unterbrochen, wenn sich das Verhalten in Manager ändert.

Stellen Sie sich das folgende Szenario vor: Die E-Mail-Adresse lautet [email protected]:

class Employee{
    String firstname, lastname;
    String getEmail() { 
        return firstname + "." + lastname + "@example.com";
    }
}

Sie haben das Gerät getestet und es funktioniert gut für Ihr Employee. Sie haben auch eine Klasse Manager erstellt:

class Manager extends Employee { /* nothing different in email generation */ }

Jetzt haben Sie eine Klasse ManagerSort, die Manager in einer Liste anhand ihrer E-Mail-Adresse sortiert. Natürlich nehmen Sie an, dass die E-Mail-Generierung dieselbe ist wie in Employee:

class ManagerSort {
    void sortManagers(Manager[] managerArrayToBeSorted)
        // sort based on email address omitted
    }
}

Sie schreiben einen Test für Ihr ManagerSort:

void testManagerSort() {
    Manager[] managers = ... // build random manager list
    ManagerSort.sortManagers(managers);

    Manager[] expected = ... // expected result
    assertEquals(expected, managers); // check the result
}

Alles funktioniert gut. Jetzt kommt jemand und überschreibt die getEmail() Methode:

class Manager extends Employee {
    String getEmail(){
        // managers should have their lastname and firstname order changed
        return lastname + "." + firstname + "@example.com";
    }
}

Was passiert nun? Ihre testManagerSort() schlägt fehl, weil die getEmail() von Manager überschrieben wurde. Sie werden in dieser Ausgabe nachforschen und die Ursache finden. Und das alles, ohne einen separaten Testfall für die geerbte Methode zu schreiben.

Daher müssen Sie geerbte Methoden nicht testen.

Andernfalls kann z.B. In Java müssten Sie alle von Object geerbten Methoden wie toString(), equals() usw. in jeder Klasse testen.

1
Uooo