4 Stimmen

Tests und Vererbungsproblem

Stellen Sie sich vor, Sie haben eine Anwendung, für die Sie Unit-Tests und Funktionstests durchführen möchten (das ist nicht schwer vorstellbar). Sie könnten eine abstrakte Klasse haben, nennen wir sie AbstractTestClass, von der alle Ihre Unit-Tests ausgehen.

AbstractTestClass würde etwa so aussehen (unter Verwendung von JUnit 4) :

class AbstractTestClass {
    boolean setupDone = false;

    @Before
    public void before() {
        if(!setupDone) {
            // insert data in db
            setupDone = true;
        }
    }
}

Ich habe mit folgenden Problemen zu kämpfen. Ich habe eine andere abstrakte Klasse, die die Web-Schnittstellen testen:

class AbstractWebTestClass extends WebTestCase {
    boolean setupDone = false;

    @Before
    public void before() {
        if(!setupDone) {
            // here, make a call to AbstractTestClass.before()
            // init the interfaces
            setupDone = true;
        }
        // do some more thing
    }
}

Es ist so ziemlich die gleiche Klasse, außer dass sie die Klasse WebTestCase . Dieser Entwurf könnte mir die Möglichkeit geben, beim Unit-Test dieselben Daten zu haben wie beim Testen der Schnittstelle.

Normalerweise sollten Sie bei solchen Problemen die Komposition der Vererbung vorziehen oder ein Strategiemuster verwenden.

Leider gefällt mir die Idee nicht ganz, in diesem speziellen Szenario Komposition gegenüber Vererbung zu bevorzugen, und ich sehe nicht, wie ich ein Strategiemuster verwenden könnte, wahrscheinlich gibt es einen Designfehler, und ich kann die Lösung nicht ganz erkennen.

Wie könnte ich diese Architektur gestalten, um mein Ziel zu erreichen?

2voto

Petro Semeniuk Punkte 6692

Ich würde dies folgendermaßen umsetzen:

class Example {

class LazyInitStrategy implements Runnable {
    private final Runnable operation;
    private boolean done = false;

    LazyInitStrategy(Runnable operation) {
        this.operation = operation;
    }

    @Override
    public void run() {
        if (!done) {
            operation.run();
            done = true;
        }
    }
}

private final class AbstractInit implements Runnable {
    public void run() {
        // insert data in db
    }
}

private final class AbstractWebInit implements Runnable {
    public void run() {
        // here, make a call to AbstractTestClass.before() init the interfaces
    }
}

class AbstractTestClass {

    final LazyInitStrategy setup = new LazyInitStrategy(new AbstractInit());

    @Before
    public void before() {
        setup.run();
        // do some more thing
    }
}

class AbstractWebTestClass extends WebTestCase {

    final LazyInitStrategy setupInfo = new LazyInitStrategy(new AbstractWebInit());

    @Before
    public void before() {
        setupInfo.run();
        // do some more thing
    }
}

}

Sicherlich ist dies eine sehr einfache Lösung, aber sie sollte die doppelte if/else-Logik zur Überprüfung, ob die Einrichtung erfolgt ist, eliminieren. Die Verwendung von Runnable ist optional, ich habe das nur zu Demozwecken gemacht, in der Realität wird man wahrscheinlich eine andere Schnittstelle verwenden.

0voto

hvgotcodes Punkte 114342

Wichtig ist, dass der Code nicht dupliziert wird. In dieser Situation würde ich eine

MyScenarioTestUtil

Klasse, die eine Reihe von statischen Methoden enthält, mit denen die Daten nach Bedarf eingerichtet werden. Sie würden die Dienstprogramm-Methoden aus dem Setup aufrufen. Auf diese Weise behalten Sie den gesamten Code an einem Ort.

Es ist wirklich nur ein semantischer Unterschied zur Verwendung von Komposition...

0voto

yegor256 Punkte 96888

Ich denke, dass das Design im Allgemeinen falsch ist. Man sollte in Unit-Tests überhaupt keine Vererbung verwenden. Tests sollten isoliert und wirklich einfach sein. Sehr oft, wie in Ihrem Fall, ist es notwendig, einige zusätzliche Objekte vorzubereiten, die den Testmethoden helfen, ihre Aufgabe zu erfüllen. In einem solchen Fall sollten Sie Ersteller solcher Objekte definieren und sie irgendwo außerhalb der Testfälle platzieren.

Zum Beispiel:

public void testMethodThatNeedsSomePreparedObjects() {
  Foo foo = new FooBuilder()
    .withFile("some-text.txt")
    .withNumber(123)
    .build();
  // now we are testing class Bar, using object of class Foo
  Bar bar = new Bar(foo);
}

Sie benötigen also FooBuilder irgendwo anders definiert werden, und diese Klasse wird die ganze Arbeit erledigen, die Sie jetzt mit Hilfe des Strategiemusters oder der Vererbung zu erledigen versuchen. Beide Ansätze sind falsch, wenn man mit Unit-Tests arbeitet.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X