Frage

Mein Motto für Java ist „nur weil Java statische Blöcke hat, heißt das noch lange nicht, dass Sie sie verwenden sollten.“ Spaß beiseite, gibt es eine Menge Tricks in Java, die einen Alptraum zu testen. Zwei der ich hasse sind Anonymous Klassen und statische Blöcke. Wir haben eine Menge von Legacy-Code, der in schriftlicher Form von Unit-Tests Verwendung von statischen Blöcke und diese sind eine der lästigen Punkte in unserer Push machen. Unser Ziel ist es, Unit-Tests für Klassen zu schreiben, die mit minimalen Code-Änderungen auf diese statischen Initialisierung ab.

Bisher mein Vorschlag meiner Kollegen ist, den Körper des statischen Blockes in eine private statische Methode zu bewegen und es staticInit nennen. Dieses Verfahren kann dann aus dem statischen Block aufgerufen werden. Für Unit-Tests eine andere Klasse, die auf dieser Klasse mit JMockit könnte leicht Mock staticInit hängt nichts zu tun. Mal sehen, dies in Beispiel.

public class ClassWithStaticInit {
  static {
    System.out.println("static initializer.");
  }
}

Wird geändert werden

public class ClassWithStaticInit {
  static {
    staticInit();
  }

  private static void staticInit() {
    System.out.println("static initialized.");
  }
}

Damit wir in einem JUnit Folgendes tun.

public class DependentClassTest {
  public static class MockClassWithStaticInit {
    public static void staticInit() {
    }
  }

  @BeforeClass
  public static void setUpBeforeClass() {
    Mockit.redefineMethods(ClassWithStaticInit.class, MockClassWithStaticInit.class);
  }
}

Doch diese Lösung kommt auch mit seinen eigenen Problemen. Sie können nicht DependentClassTest und ClassWithStaticInitTest auf derselben JVM ausgeführt werden, da Sie tatsächlich der statische Block für ClassWithStaticInitTest ausgeführt werden sollen.

Was wäre Ihr Weg, um diese Aufgabe zu erledigen? Oder besser, nicht-JMockit basierte Lösungen, die Sie denken, saubere funktionieren würde?

War es hilfreich?

Lösung

Als ich in dieses Problem, ich in der Regel die gleiche Sache, die Sie beschreiben, außer ich die statische Methode machen geschützt, so kann ich es manuell aufrufen. Hinzu kommt, stelle ich sicher, dass das Verfahren mehrere Male ohne Probleme aufgerufen werden kann (sonst ist es nicht besser als der statische Initialisierer soweit die Tests gehen).

Das funktioniert recht gut, und ich kann tatsächlich testen, dass die statische Initialisierer Methode tut, was ich erwarte / will, es zu tun. Manchmal ist es nur einfachste einigen statische Initialisierung Code zu haben, und es ist einfach nicht wert ein übermäßig komplexes System zu bauen, sie zu ersetzen.

Wenn ich diesen Mechanismus verwenden, stelle ich sicher, zu dokumentieren, dass die geschützte Methode nur zu Testzwecken ausgesetzt ist, mit der Hoffnung, dass es nicht von anderen Entwicklern verwendet werden. Dies kann natürlich keine gangbare Lösung sein, zum Beispiel, wenn die Klasse Schnittstelle von außen sichtbar ist (entweder als Subkomponente irgendeine Art für andere Teams oder als öffentlicher Rahmen). Es ist eine einfache Lösung für das Problem aber, und nicht ein Drittes Bibliothek erfordern einzurichten (die Ich mag).

Andere Tipps

PowerMock ist ein weiterer Mock-Framework, das EasyMock und Mockito erstreckt. Mit PowerMock können Sie unerwünschtes Verhalten aus einer Klasse zu entfernen, zum Beispiel eine statische Initialisierer . In Ihrem Beispiel fügen Sie einfach die folgenden Anmerkungen zu Ihrem JUnit-Testfall:

@RunWith(PowerMockRunner.class)
@SuppressStaticInitializationFor("some.package.ClassWithStaticInit")

PowerMock kein Java-Agenten verwenden und daher keine Änderung erfordert der JVM-Startparameter. Sie einfach die JAR-Datei und die oben genannten Anmerkungen hinzufügen.

Das wird in mehr „Advanced“ JMockit erhalten. Es stellt sich heraus, Sie statische Initialisierungsblöcke in JMockit durch ein public void $clinit() Verfahren zu schaffen neu definieren können. Anstatt also diese Änderung

public class ClassWithStaticInit {
  static {
    staticInit();
  }

  private static void staticInit() {
    System.out.println("static initialized.");
  }
}

können wir auch ClassWithStaticInit lassen wie es ist und nicht nach dem in der MockClassWithStaticInit:

public static class MockClassWithStaticInit {
  public void $clinit() {
  }
}

Dies wird in der Tat erlaubt es uns nicht, um Änderungen in den bestehenden Klassen zu machen.

Gelegentlich finde ich statischen initilizers in Klassen, die mein Code abhängt. Wenn ich nicht den Code Refactoring kann ich verwenden PowerMock 's @SuppressStaticInitializationFor Annotation die statischen Initialisierer zu unterdrücken:

@RunWith(PowerMockRunner.class)
@SuppressStaticInitializationFor("com.example.ClassWithStaticInit")
public class ClassWithStaticInitTest {

    ClassWithStaticInit tested;

    @Before
    public void setUp() {
        tested = new ClassWithStaticInit();
    }

    @Test
    public void testSuppressStaticInitializer() {
        asserNotNull(tested);
    }

    // more tests...
}

Lesen Sie mehr über Unterdrückung von unerwünschten Verhalten .

Disclaimer: PowerMock ist ein Open-Source-Projekt von zwei Kollegen von mir entwickelt.

Klingt für mich wie Sie einen Symptom behandeln: schlechtes Design mit Abhängigkeiten von statischer Initialisierung. Vielleicht ist etwas Refactoring die wirkliche Lösung. Es klingt wie Sie bereits ein wenig Refactoring mit staticInit() Funktion getan haben, aber vielleicht muss diese Funktion vom Konstruktor, nicht von einem statischen Initialisierer aufgerufen werden. Wenn Sie mit statischen Initialisierungen Zeit abschaffen können, werden Sie besser dran. Nur können Sie diese Entscheidung treffen ( Ich kann nicht sehen Ihre Code-Basis ), aber einige Refactoring wird auf jeden Fall helfen.

Wie für spöttische, verwende ich EasyMock, aber ich habe in der gleichen Ausgabe führen. Nebenwirkungen von statischen Initialisierungen in Legacy-Code machen die Prüfung schwierig. Unsere Antwort war die statischen Initialisierer Refactoring aus.

Sie können Ihren Testcode in Groovy schreiben und einfach die statische Methode mit metaprogramming verspotten.

Math.metaClass.'static'.max = { int a, int b -> 
    a + b
}

Math.max 1, 2

Wenn Sie nicht Groovy verwenden können, werden Sie wirklich benötigen Sie den Code Refactoring (vielleicht so etwas wie ein initializator zu injizieren).

Mit freundlichen Grüßen

Ich nehme an, Sie wirklich eine Art Fabrik statt des statischen Initialisierer wollen.

Einige Mischung aus Singleton und eine abstrakte Fabrik würde wahrscheinlich in der Lage sein, Ihnen die gleiche Funktionalität wie heute zu erhalten, und mit guten Testbarkeit, aber das wäre ziemlich viel Kesselblech Code hinzufügen, so könnte es besser sein, nur versuche die statischen Sachen weg vollständig zu umgestalten oder wenn Sie zumindest mit etwas weniger komplexen Lösung wegkommen können.

Schwer zu sagen, ob Es ist möglich, ohne den Code allerdings zu sehen.

ich nicht super gut informiert bin in Mock Frameworks mir bitte so korrigieren, wenn ich falsch bin, konnte aber nicht, dass Sie möglicherweise zwei verschiedene Mock-Objekte müssen die Situationen abdecken, die Sie erwähnen? Wie

public static class MockClassWithEmptyStaticInit {
  public static void staticInit() {
  }
}

und

public static class MockClassWithStaticInit {
  public static void staticInit() {
    System.out.println("static initialized.");
  }
}

Dann können Sie sie in Ihren verschiedenen Testfälle verwenden

@BeforeClass
public static void setUpBeforeClass() {
  Mockit.redefineMethods(ClassWithStaticInit.class, 
                         MockClassWithEmptyStaticInit.class);
}

und

@BeforeClass
public static void setUpBeforeClass() {
  Mockit.redefineMethods(ClassWithStaticInit.class, 
                         MockClassWithStaticInit.class);
}

ist.

Nicht wirklich eine Antwort, aber nur gefragt - ist nicht es eine Möglichkeit, „rückwärts“ der Aufruf an Mockit.redefineMethods
Wenn keine solche explizite Methode vorhanden ist, sollte es nicht wieder in der folgenden Art und Weise der Ausführung der Trick?

Mockit.redefineMethods(ClassWithStaticInit.class, ClassWithStaticInit.class);

Wenn ein solches Verfahren existiert, können Sie es in der Klasse ausführen kann @AfterClass Verfahren und Test ClassWithStaticInitTest mit dem ‚Original‘ statischen Initialisierungsblocks, als ob sich nichts geändert hat, von der gleichen JVM.

Dies ist nur ein Gefühl, obwohl, so dass ich vielleicht etwas fehlen.

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