Question

Ma devise pour Java est "juste parce que Java a des blocs statiques, cela ne signifie pas que vous devriez les utiliser." Blagues à part, il y a beaucoup de trucs en Java qui font du test un cauchemar.Deux des classes que je déteste le plus sont les classes anonymes et les blocs statiques.Nous avons beaucoup de code existant qui utilise des blocs statiques et c'est l'un des points ennuyeux de notre effort d'écriture de tests unitaires.Notre objectif est de pouvoir écrire des tests unitaires pour les classes qui dépendent de cette initialisation statique avec un minimum de modifications de code.

Jusqu'à présent, ma suggestion à mes collègues est de déplacer le corps du bloc statique vers une méthode statique privée et de l'appeler staticInit.Cette méthode peut ensuite être appelée depuis le bloc statique.Pour les tests unitaires, une autre classe qui dépend de cette classe pourrait facilement se moquer staticInit avec JMockit pour ne rien faire.Voyons cela dans un exemple.

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

Sera changé en

public class ClassWithStaticInit {
  static {
    staticInit();
  }

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

Afin que nous puissions faire ce qui suit dans un JUnit.

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

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

Cependant, cette solution comporte également ses propres problèmes.Tu ne peux pas courir DependentClassTest et ClassWithStaticInitTest sur la même JVM puisque vous voulez réellement que le bloc statique s'exécute pendant ClassWithStaticInitTest.

Quelle serait votre manière d’accomplir cette tâche ?Ou de meilleures solutions non basées sur JMockit qui, selon vous, fonctionneraient plus proprement ?

Était-ce utile?

La solution

Lorsque je rencontre ce problème, je fais généralement la même chose que vous décrivez, sauf que je protège la méthode statique afin de pouvoir l'invoquer manuellement.En plus de cela, je m'assure que la méthode peut être invoquée plusieurs fois sans problème (sinon, ce n'est pas mieux que l'initialiseur statique en ce qui concerne les tests).

Cela fonctionne assez bien et je peux réellement tester que la méthode d'initialisation statique fait ce que j'attends/veux qu'elle fasse.Parfois, il est tout simplement plus simple d'avoir du code d'initialisation statique, et cela ne vaut tout simplement pas la peine de construire un système trop complexe pour le remplacer.

Lorsque j'utilise ce mécanisme, je m'assure de documenter que la méthode protégée n'est exposée qu'à des fins de test, dans l'espoir qu'elle ne sera pas utilisée par d'autres développeurs.Bien sûr, cela peut ne pas être une solution viable, par exemple si l'interface de la classe est visible de l'extérieur (soit en tant que sous-composant pour d'autres équipes, soit en tant que framework public).Il s’agit cependant d’une solution simple au problème et ne nécessite pas la configuration d’une bibliothèque tierce (ce que j’aime bien).

Autres conseils

PowerMock est un autre framework fictif qui étend EasyMock et Mockito.Avec PowerMock, vous pouvez facilement supprimer les comportements indésirables à partir d'une classe, par exemple un initialiseur statique.Dans votre exemple, vous ajoutez simplement les annotations suivantes à votre scénario de test JUnit :

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

PowerMock n'utilise pas d'agent Java et ne nécessite donc pas de modification des paramètres de démarrage de la JVM.Vous ajoutez simplement le fichier jar et les annotations ci-dessus.

Cela va entrer dans un JMockit plus "avancé".Il s'avère que vous pouvez redéfinir les blocs d'initialisation statiques dans JMockit en créant un public void $clinit() méthode.Donc, au lieu de faire ce changement

public class ClassWithStaticInit {
  static {
    staticInit();
  }

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

autant partir ClassWithStaticInit tel quel et procédez comme suit dans le MockClassWithStaticInit:

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

Cela nous permettra en effet de n'apporter aucune modification aux classes existantes.

Parfois, je trouve des initiateurs statiques dans les classes dont dépend mon code.Si je ne peux pas refactoriser le code, j'utilise PowerMockc'est @SuppressStaticInitializationFor annotation pour supprimer l'initialiseur statique :

@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...
}

En savoir plus sur supprimer les comportements indésirables.

Clause de non-responsabilité:PowerMock est un projet open source développé par deux de mes collègues.

Il me semble que vous traitez un symptôme :mauvaise conception avec des dépendances sur l'initialisation statique.Peut-être qu'une refactorisation est la vraie solution.Il semble que vous ayez déjà effectué une petite refactorisation avec votre staticInit() fonction, mais peut-être que cette fonction doit être appelée depuis le constructeur, pas depuis un initialiseur statique.Si vous pouvez supprimer la période d’initialisation statique, vous vous en porterez mieux.Vous seul pouvez prendre cette décision (Je ne vois pas votre base de code) mais une refactorisation sera certainement utile.

Quant à la moquerie, j'utilise EasyMock, mais j'ai rencontré le même problème.Les effets secondaires des initialiseurs statiques dans le code existant rendent les tests difficiles.Notre réponse a été de refactoriser l'initialiseur statique.

Vous pouvez écrire votre code de test dans Groovy et vous moquer facilement de la méthode statique à l'aide de la métaprogrammation.

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

Math.max 1, 2

Si vous ne pouvez pas utiliser Groovy, vous devrez vraiment refactoriser le code (peut-être pour injecter quelque chose comme un initialiseur).

Cordialement

Je suppose que vous voulez vraiment une sorte d'usine au lieu de l'initialiseur statique.

Un mélange d'un singleton et d'une usine abstraite serait probablement en mesure de vous offrir les mêmes fonctionnalités qu'aujourd'hui, et avec une bonne testabilité, mais cela ajouterait beaucoup de code passe-partout, il serait donc peut-être préférable d'essayer simplement de refactoriser les éléments statiques complètement ou si vous pouviez au moins vous en sortir avec une solution moins complexe.

Difficile de dire si c'est possible sans voir votre code.

Je ne connais pas très bien les frameworks Mock, alors corrigez-moi si je me trompe, mais ne pourriez-vous pas avoir deux objets Mock différents pour couvrir les situations que vous mentionnez ?Tel que

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

et

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

Vous pourrez ensuite les utiliser dans vos différents cas de tests

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

et

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

respectivement.

Pas vraiment une réponse, mais je me demande simplement : n'y a-t-il aucun moyen d'"inverser" l'appel à Mockit.redefineMethods?
Si aucune méthode explicite n’existe, l’exécuter à nouveau de la manière suivante ne devrait-il pas suffire ?

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

Si une telle méthode existe, vous pouvez l'exécuter dans la classe ' @AfterClass méthode et test ClassWithStaticInitTest avec le bloc d'initialisation statique "original", comme si rien n'avait changé, de la même JVM.

Ce n'est qu'une intuition, donc il me manque peut-être quelque chose.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top