2009-02-19 10 views
49

stiamo usando Spring per i miei scopi applicativi e il framework Spring Testing per i test unitari. Tuttavia, abbiamo un piccolo problema: il codice dell'applicazione carica un contesto di applicazione Spring da un elenco di posizioni (file xml) nel classpath. Ma quando eseguiamo i nostri test unitari, vogliamo che alcuni dei bean Spring siano mock invece di classi di implementazione a pieno titolo. Inoltre, per alcuni test unitari vogliamo che alcuni bean diventino dei mock, mentre per altri test unitari vogliamo che altri bean diventino dei mock, poiché stiamo testando diversi livelli dell'applicazione.La ridefinizione dei fagioli di primavera nell'ambiente di test dell'unità

Tutto ciò significa che voglio ridefinire i bean specifici del contesto dell'applicazione e aggiornare il contesto quando lo si desidera. Durante questa operazione, desidero ridefinire solo una piccola parte dei bean che si trovano in uno (o più) file di definizione dei bean xml originali. Non riesco a trovare un modo semplice per farlo. È sempre stato considerato che la primavera è un'unità che collauda un quadro amichevole, quindi mi manca qualcosa qui.

Avete qualche idea su come farlo?

Grazie.

+8

La primavera rende semplice il test dell'unità. Questa è la parte che ti manca: stai facendo un test di integrazione e non un test unitario. In un vero Unit Test, qualsiasi bean dipendente dovrebbe essere un mock, perché si sta solo testando un'unità e non un intero sistema. – bpapa

+0

Come * unit test * un'applicazione primaverile: http://confessionsofanagilecoach.blogspot.com/2016/05/unit-testing-spring-applications.html –

risposta

18

vorrei proporre un costume TestClass e alcune regole semplici per le posizioni della molla bean.xml

@RunWith(SpringJUnit4ClassRunner.class) 
@ContextConfiguration(locations = { 
    "classpath*:spring/*.xml", 
    "classpath*:spring/persistence/*.xml", 
    "classpath*:spring/mock/*.xml"}) 
@Transactional 
@TestExecutionListeners({ 
    DependencyInjectionTestExecutionListener.class, 
    TransactionalTestExecutionListener.class, 
    DirtiesContextTestExecutionListener.class}) 
public abstract class AbstractHibernateTests implements ApplicationContextAware 
{ 

    /** 
    * Logger for Subclasses. 
    */ 
    protected final Logger LOG = LoggerFactory.getLogger(getClass()); 

    /** 
    * The {@link ApplicationContext} that was injected into this test instance 
    * via {@link #setApplicationContext(ApplicationContext)}. 
    */ 
    protected ApplicationContext applicationContext; 

    /** 
    * Set the {@link ApplicationContext} to be used by this test instance, 
    * provided via {@link ApplicationContextAware} semantics. 
    */ 
    @Override 
    public final void setApplicationContext(
      final ApplicationContext applicationContext) { 
     this.applicationContext = applicationContext; 
    } 
} 

se ci sono mock bean.xml nella posizione specificata, essi sovrascriveranno tutti i bean.xml "reali" nelle posizioni "normali" - le posizioni normali potrebbero essere diverse

ma ...non mischiarei mai mock e non-mock bean è difficile tracciare i problemi, quando l'applicazione invecchia.

3

Facile. Si utilizza un contesto applicativo personalizzato per i test di unità. O non ne usi affatto e crei e inietti manualmente i fagioli.

Mi sembra che il test potrebbe essere un po 'troppo ampio. Il test unitario riguarda test, bene, unità. Un bean Spring è un buon esempio di unità. Non dovresti aver bisogno di un intero contesto applicativo per quello. Trovo che se il tuo test unitario è così alto che hai bisogno di centinaia di bean, connessioni al database, ecc. Allora hai un test unitario davvero fragile che si interromperà nel prossimo cambiamento, sarà difficile da mantenere e davvero non lo è t aggiungendo molto valore.

+0

Hai ragione, stiamo utilizzando piuttosto test di sistema e non test di unità , testiamo effettivamente il flusso dall'API al DB sul nostro server delle applicazioni. Mi rendo conto che scrivere unit test è un modo migliore, ma non è fattibile in questo momento. – Stas

0

Forse potresti utilizzare qualificatori per i tuoi bean? Dovresti ridefinire i bean che vuoi creare in un contesto applicativo separato e etichettarli con un "test" del qualificatore. Nei test delle unità, quando si collegano i bean, specificare sempre il qualificatore "test" per utilizzare i modelli.

2

È possibile utilizzare la funzione import nel contesto dell'app di test per caricare i pungoli e ignorare quelli desiderati. Ad esempio, la mia origine dati prod è solitamente acquisita tramite la ricerca JNDI, ma quando eseguo il test, utilizzo una fonte dati DriverManager, quindi non devo avviare il server dell'app per testare.

4

È possibile anche scrivere il test di unità di non richiede alcuna ricerche a tutti:

@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() { 
     ... 
    } 
} 

Questo offre un modo semplice per combinare i file di configurazione vera e propria con i file di configurazione di prova. Ad esempio, quando si utilizza la sospensione, potrei avere il bean sessionFactory in un file di configurazione (da utilizzare sia nei test che nell'app principale) e avere il bean dataSource in un altro file di configurazione (si potrebbe usare DriverManagerDataSource a un db in memoria, l'altro potrebbe utilizzare una ricerca JNDI).

Ma, sicuramente tener conto di prendere @cletus's avvertimento ;-)

+0

questo porterebbe a molti bean.xml duplicati per i test, per test di mock-only preferirei proporre semplici schemi simulati easyMock o similari –

+0

Sì, alcuni dei nostri test utilizzavano EasyMock invece di questo approccio. IMHO EasyMock tende a brillare per veri test di unità. Ulteriori test "integrali" traggono vantaggio dall'approccio ContextConfiguration. – toolkit

16

Una delle ragioni per cui la primavera è descritta come "test-friendly" è perché potrebbe essere facile solo il nuovo o roba finta nel test dell'unità.

In alternativa abbiamo usato la seguente configurazione con grande successo, e penso che è abbastanza vicino a quello che si vuole, vorrei fortemente raccomando:

Per tutti i fagioli che necessitano di diverse implementazioni in contesti diversi, passare al cablaggio basato sull'annotazione. Puoi lasciare gli altri così come sono.

Implementare la seguente serie di annotazioni

<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> 

Poi di annotare le vivo implementazioni con @Repository, il vostro stub implementazioni con @StubRepository, qualsiasi codice che dovrebbe essere presente nel dispositivo di unità di test SOLO con @TestScopedComponent. Potresti incorrere in un paio di ulteriori annotazioni, ma sono un ottimo inizio.

Se hai un sacco di spring.xml, probabilmente avrai bisogno di creare alcuni nuovi file xml primaverili che fondamentalmente contengono solo le definizioni di scansione dei componenti. Normalmente dovresti semplicemente aggiungere questi file al tuo normale elenco @ContextConfiguration. La ragione di ciò è dovuta al fatto che spesso si ottengono configurazioni diverse delle scansioni del contesto (fiducia in me, corrisponderà a almeno 1 altra annotazione se esegui test web, che rende 4 combinazioni pertinenti)

quindi si utilizza fondamentalmente la

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

si noti che questa impostazione non non consentono di avere alternati combinazioni di dati stub/live. L'abbiamo provato, e penso che ciò abbia provocato un casino che non consiglierei a nessuno;) o colleghiamo il set completo di stub o il set completo di servizi live.

Principalmente usiamo le dipendenze di stub auto-cablate quando testiamo gui vicino a cose in cui le dipendenze sono in genere piuttosto consistenti. Nelle aree più pulite del codice utilizziamo test unitari più regolari.

Nel nostro sistema abbiamo i seguenti file XML per il componente-scan:

  • per la produzione di web regolare
  • per iniziare web con mozziconi solo
  • per i test di integrazione (in JUnit)
  • per i test unitari (in junit)
  • per prove web selenio (a) junit

Ciò significa che abbiamo totalmente 5 diverse configurazioni a livello di sistema con cui possiamo avviare l'applicazione. Poiché usiamo solo annotazioni, la molla è abbastanza veloce da rendere autowire anche quei test unitari che vogliamo cablati. So che non è tradizionale, ma è davvero grandioso.

Out test di integrazione gestita con piena messa a punto dal vivo, e una o due volte ho deciso di ottenere davvero pragmatico e vogliono avere un 5 cablaggi live e un singolo finto:

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); 
    } 


    } 
} 

So che il test i puristi mi staranno addosso per questo. Ma a volte è solo una soluzione molto pragmatica che risulta essere molto elegante quando l'alternativa sarebbe davvero brutta. Di nuovo è di solito in quelle zone spericolate.

+0

Solo curioso, questo non porta a qualche problema di manutenzione per quanto riguarda le singole @ContextConfiguration per Testclass? –

+0

No, abbiamo solo 5 @configurationContxts * a livello di sistema * totalmente * in una grande applicazione. Ho modificato nuovamente per renderlo più chiaro. – krosenvold

+0

Grazie per questa risposta, l'ho applicata con successo. Una cosa da notare è che questo modo di procedere è molto flessibile perché è possibile utilizzare diversi contesti: i tag di scansione dei componenti (ad esempio uno per modulo o per pacchetto). Inoltre, potrebbe essere utile impostare 'use-default-filters = "false". –

0

Voglio fare la stessa cosa, e lo stiamo trovando essenziale.

L'attuale meccanismo che usiamo è piuttosto manuale ma funziona.

Ad esempio, si desidera prendere in giro il bean di tipo Y. Ciò che facciamo è ogni bean con quella dipendenza che implementiamo un'interfaccia - "IHasY". Questa interfaccia è

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

Poi, nel nostro test abbiamo chiamato il metodo 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); 
     } 
    } 

Non voglio creare un file XML intero solo per iniettare questa nuova dipendenza e che è per questo che come questo.

Se si è disposti a creare un file di configurazione xml, la strada da percorrere sarebbe quella di creare un nuovo stabilimento con i mock bean e rendere il factory predefinito un elemento principale di questo factory. Assicurati quindi di caricare tutti i tuoi fagioli dalla nuova fabbrica secondaria. Quando si esegue questa operazione, la sub-factory sostituirà i bean nella factory padre quando gli ID bean sono gli stessi.

Ora se, nel mio test, se potessi programmaticamente creare una fabbrica, sarebbe fantastico. Dover usare xml è troppo ingombrante. Sto cercando di creare quella fabbrica di bambini con il codice. Quindi ogni test può configurare la sua fabbrica nel modo desiderato. Non c'è motivo per cui una fabbrica del genere non funzioni.

6

Ci sono alcune soluzioni molto complicate e potenti elencate qui.

Ma c'è un modo FAR, FAR più semplice per ottenere ciò che Stas ha richiesto, che non implica la modifica di una riga di codice nel metodo di prova. Funziona allo stesso modo per test di unità e test di integrazione primaverile, per dipendenze autowired, campi privati ​​e protetti.

Eccolo:

junitx.util.PrivateAccessor.setField(testSubject, "fieldName", mockObject); 
+3

Oppure, se stai usando spring: 'org.springframework.test.util.ReflectionTestUtils.setField (testSubject," fieldName ", mockObject);' – DrUseful

+2

assumendo che tu abbia un facile accesso all'oggetto che ottiene le dipendenze iniettate. –

+0

Ciò presuppone che la classe using non acceda alla classe utilizzata nel suo costruttore o nel metodo init. In tal caso, la sostituzione del campo del fagiolo tramite il riflesso avverrebbe troppo tardi. –

1

Non ho i punti reputazione ad accumulare risposta di duffymo, ma volevo solo a suonare e dire la sua era la risposta "giusta" per me.

Creare un'istanza di FileSystemXmlApplicationContext nella configurazione dell'unità di test con un'applicazione personalizzataContext.xml. In questo xml personalizzato, nella parte superiore, fai come indica duffymo. Quindi dichiara i tuoi mock bean, le origini dati non JNDI, ecc. Che sovrascriveranno l'ID dichiarato nell'importazione.

Ha funzionato come un sogno per me.

7

Vai a questa tutorial with @InjectedMock annotation

Mi ha salvato un sacco di tempo.Devi solo usare

@Mock 
SomeClass mockedSomeClass 

@InjectMock 
ClassUsingSomeClass service 

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

e tutti i tuoi problemi sono risolti. Mockito sostituirà l'iniezione a dipendenza di primavera con una finta. L'ho usato da solo e funziona benissimo.

0

Dal momento che l'OP questo è venuto lungo: Springockito

1

Non è necessario utilizzare alcun contesti di prova (non importa si basa XML o Java). Dal momento che Spring boot 1.4 è disponibile una nuova annotazione @MockBean che ha introdotto il supporto nativo per il mocking e Spying of Spring Beans.

Problemi correlati