web-dev-qa-db-fra.com

Redéfinition des haricots de printemps dans l'environnement de test unitaire

nous utilisons Spring pour mes applications et le framework Spring Testing pour les tests unitaires. Nous avons cependant un petit problème: le code d'application charge un contexte d'application Spring à partir d'une liste d'emplacements (fichiers xml) dans le chemin de classe. Mais lorsque nous exécutons nos tests unitaires, nous voulons que certains des beans Spring soient des simulateurs au lieu de classes d'implémentation à part entière. De plus, pour certains tests unitaires, nous voulons que certains beans deviennent des simulations, tandis que pour d'autres tests unitaires, nous voulons que d'autres beans deviennent des simulations, car nous testons différentes couches de l'application.

Tout cela signifie que je veux redéfinir des beans spécifiques du contexte de l'application et actualiser le contexte si vous le souhaitez. En faisant cela, je veux redéfinir seulement une petite partie des beans situés dans un (ou plusieurs) fichier de définition de beans xml d'origine. Je ne trouve pas de moyen facile de le faire. Il est toujours considéré que Spring est un cadre convivial pour les tests unitaires, donc je dois manquer quelque chose ici.

Avez-vous des idées pour le faire?

Merci.

50
Stas

L'une des raisons pour lesquelles le printemps est décrit comme compatible avec les tests est qu'il peut être facile de simplement nouvea ou de se moquer des choses dans le test unitaire.

Alternativement, nous avons utilisé la configuration suivante avec beaucoup de succès, et je pense qu'elle est assez proche de ce que vous voulez, je le recommande fortement:

Pour tous les beans qui nécessitent différentes implémentations dans différents contextes, passez au câblage basé sur des annotations. Vous pouvez laisser les autres tels quels.

Implémentez l'ensemble d'annotations suivant

 <context:component-scan base-package="com.foobar">
     <context:include-filter type="annotation" expression="com.foobar.annotations.StubRepository"/>
     <context:include-filter type="annotation" expression="com.foobar.annotations.TestScopedComponent"/>
     <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
 </context:component-scan>

Ensuite, vous annotez vos implémentations live avec @Repository, vos implémentations stub avec @StubRepository, tout code qui devrait être présent dans le montage de test unitaire UNIQUEMENT avec @TestScopedComponent. Vous pourriez avoir besoin de quelques annotations supplémentaires, mais ce sont des bons débuts.

Si vous avez beaucoup de spring.xml, vous devrez probablement créer quelques nouveaux fichiers xml de printemps qui ne contiennent essentiellement que les définitions d'analyse des composants. Vous devez normalement simplement ajouter ces fichiers à votre liste habituelle de @ContextConfiguration. La raison en est que vous vous retrouvez fréquemment avec différentes configurations des analyses de contexte (croyez-moi, vous allez faites au moins 1 annotations supplémentaires si vous faites des tests Web, ce qui fait 4 combinaisons pertinentes)

Ensuite, vous utilisez essentiellement le

@ContextConfiguration(locations = { "classpath:/path/to/root-config.xml" })
@RunWith(SpringJUnit4ClassRunner.class)

Notez que cette configuration ne pas vous permet d'avoir des combinaisons alternées de données stub/live. Nous avons essayé cela, et je pense que cela a abouti à un gâchis que je ne recommanderais à personne;) Nous câblons l'ensemble complet des talons ou l'ensemble complet des services en direct.

Nous utilisons principalement les dépendances de stub auto-câblées lors du test de l'interface graphique près de choses où les dépendances sont généralement assez importantes. Dans les zones plus propres du code, nous utilisons des tests unitaires plus réguliers.

Dans notre système, nous avons les fichiers xml suivants pour l'analyse des composants:

  • pour une production Web régulière
  • pour les tests d'intégration (en junit)
  • pour les tests unitaires (en junit)
  • pour les tests web Selenium (en junit)

Cela signifie que nous avons totalement 5 configurations différentes à l'échelle du système avec lesquelles nous pouvons démarrer l'application. Comme nous n'utilisons que des annotations, le ressort est assez rapide pour câbler automatiquement même les tests unitaires que nous voulons câblés. Je sais que ce n'est pas traditionnel, mais c'est vraiment génial.

Nos tests d'intégration sont exécutés avec une configuration en direct complète, et une ou deux fois, j'ai décidé d'obtenir vraiment pragmatique et je veux avoir 5 câblages en direct et une seule maquette:

public class HybridTest {
   @Autowired
   MyTestSubject myTestSubject;


   @Test
   public void testWith5LiveServicesAndOneMock(){
     MyServiceLive service = myTestSubject.getMyService();
     try {
          MyService mock = EasyMock.create(...)
          myTestSubject.setMyService( mock);

           .. do funky test  with lots of live but one mock object

     } finally {
          myTestSubject.setMyService( service);
     }


   }
}

Je sais que les puristes du test vont être partout pour moi. Mais parfois, c'est juste une solution très pragmatique qui s'avère très élégante alors que l'alternative serait vraiment très moche. Encore une fois, c'est généralement dans ces zones proches.

16
krosenvold

Voir ceci tutoriel avec annotation @InjectedMock

Cela m'a fait gagner beaucoup de temps. Vous utilisez simplement

@Mock
SomeClass mockedSomeClass

@InjectMock
ClassUsingSomeClass service

@Before
public void setUp() {
    MockitoAnnotations.initMocks(this);
}

et tous vos problèmes sont résolus. Mockito remplacera l'injection de dépendance à ressort par une maquette. Je l'ai utilisé moi-même et cela fonctionne très bien.

7
Ev0oD

Il existe des solutions très compliquées et puissantes répertoriées ici.

Mais il existe un moyen FAR, FAR plus simple d'accomplir ce que Stas a demandé, ce qui n'implique pas de modifier autre chose qu'une ligne de code dans la méthode de test. Il fonctionne aussi bien pour les tests unitaires que pour les tests d'intégration Spring, pour les dépendances câblées automatiquement, les champs privés et protégés.

C'est ici:

junitx.util.PrivateAccessor.setField(testSubject, "fieldName", mockObject);
6
Daniel Alexiuc

Vous pouvez également écrire vos tests unitaires pour ne nécessiter aucune recherche:

@ContextConfiguration(locations = { "classpath:/path/to/test-config.xml" })
@RunWith(SpringJUnit4ClassRunner.class)
public class MyBeanTest {

    @Autowired
    private MyBean myBean; // the component under test

    @Test
    public void testMyBean() {
        ...
    }
}

Cela donne un moyen facile de mélanger et de faire correspondre de vrais fichiers de configuration avec des fichiers de configuration de test.

Par exemple, lorsque j'utilise hibernate, je peux avoir mon bean sessionFactory dans un fichier de configuration (à utiliser à la fois dans les tests et dans l'application principale) et avoir par le bean dataSource dans un autre fichier de configuration (on peut utiliser un DriverManagerDataSource pour mémoire db, l'autre peut utiliser une recherche JNDI).

Mais, prenez certainement en compte @ cletus's avertissement ;-)

4
toolkit

Facile. Vous utilisez un contexte d'application personnalisé pour vos tests unitaires. Ou vous n'en utilisez pas du tout et vous créez et injectez manuellement vos beans.

Il me semble que vos tests pourraient être un peu trop larges. Les tests unitaires concernent les tests, enfin, les unités. Un haricot de printemps est un assez bon exemple d'unité. Vous ne devriez pas avoir besoin d'un contexte d'application complet pour cela. Je trouve que si vos tests unitaires sont si élevés que vous avez besoin de centaines de beans, de connexions à la base de données, etc., vous avez un test unitaire vraiment fragile qui va se casser lors du prochain changement, sera difficile à maintenir et ne l'est vraiment pas t ajouter beaucoup de valeur.

3
cletus

Vous pouvez utiliser la fonction import dans le contexte de votre application de test pour charger les beans prod et remplacer ceux que vous voulez. Par exemple, ma source de données prod est généralement acquise via la recherche JNDI, mais lorsque je teste, j'utilise une source de données DriverManager, donc je n'ai pas à démarrer le serveur d'applications pour tester.

2
duffymo

Vous n'avez pas besoin d'utiliser de contexte de test (peu importe XML ou Java). Depuis Spring boot 1.4, il existe une nouvelle annotation disponible @MockBean qui a introduit la prise en charge native de la moquerie et de l'espionnage des haricots de printemps.

1
luboskrnac

Depuis l'OP, cela s'est produit: Springockito

0
Michael Wiles

Peut-être pourriez-vous utiliser des qualificatifs pour vos beans? Vous redéfiniriez les beans que vous souhaitez simuler dans un contexte d'application distinct et les étiquetteriez avec un qualificatif "test". Dans vos tests unitaires, lors du câblage de vos beans, spécifiez toujours le qualificatif "test" pour utiliser les maquettes.

0
Il-Bhima

spring-reinject est conçu pour remplacer les haricots par des simulacres.

0
Sergey Grigoriev

Je veux faire la même chose, et nous le trouvons essentiel.

Le mécanisme actuel que nous utilisons est assez manuel mais il fonctionne.

Supposons, par exemple, que vous souhaitiez simuler un bean de type Y. Ce que nous faisons, c'est que chaque bean ayant cette dépendance, nous faisons implémenter une interface - "IHasY". Cette interface est

interface IHasY {
   public void setY(Y y);
}

Ensuite, dans notre test, nous appelons la méthode util ...

 public static void insertMock(Y y) {
        Map invokers = BeanFactory.getInstance().getFactory("core").getBeansOfType(IHasY.class);
        for (Iterator iterator = invokers.values().iterator(); iterator.hasNext();) {
            IHasY invoker = (IHasY) iterator.next();
            invoker.setY(y);
        }
    }

Je ne veux pas créer un fichier xml entier juste pour injecter cette nouvelle dépendance et c'est pourquoi j'aime ça.

Si vous êtes prêt à créer un fichier de configuration xml, alors la voie à suivre serait de créer une nouvelle usine avec les mock beans et de faire de votre usine par défaut un parent de cette usine. Assurez-vous alors que vous chargez tous vos beans de la nouvelle usine enfant. En faisant cela, la sous-fabrique remplacera les beans dans la fabrique parent lorsque les ID de bean sont les mêmes.

Maintenant, si, dans mon test, si je pouvais par programme créer une usine, ce serait génial. Devoir utiliser xml est tout simplement trop lourd. Je cherche à créer cette usine enfant avec du code. Ensuite, chaque test peut configurer son usine comme il le souhaite. Il n'y a aucune raison pour qu'une telle usine ne fonctionne pas.

0
Michael Wiles