Perché Mockito non prende in giro i metodi statici?


276

Ho letto alcuni thread qui sui metodi statici e penso di aver compreso i problemi che un uso improprio / eccessivo di metodi statici può causare. Ma non ho davvero capito perché è difficile prendere in giro i metodi statici.

So che altri framework beffardi, come PowerMock, possono farlo, ma perché non può farlo Mockito?

Ho letto questo articolo , ma l'autore sembra essere religiosamente contrario alla parola static, forse è la mia scarsa comprensione.

Una semplice spiegazione / collegamento sarebbe fantastico.


12
Solo una nota a margine: PowerMock non è una libreria di oggetti fittizi di per sé, aggiunge semplicemente queste funzionalità (statiche e ctor derisori) sopra altre librerie. Usiamo PowerMock + Mockito al lavoro, galleggiano bene l'uno con l'altro.
Matthias

Risposte:


242

Penso che il motivo potrebbe essere che le librerie di oggetti fittizi in genere creano mock creando dinamicamente classi in fase di esecuzione (utilizzando cglib ). Ciò significa che implementano un'interfaccia in fase di esecuzione (è quello che fa EasyMock se non sbaglio) o ereditano dalla classe per deridere (questo è ciò che fa Mockito se non sbaglio). Entrambi gli approcci non funzionano per i membri statici, poiché non è possibile sovrascriverli utilizzando l'ereditarietà.

L'unico modo per simulare la statica è modificare il byte code di una classe in fase di esecuzione, che suppongo sia un po 'più complicato dell'ereditarietà.

Questa è la mia ipotesi, per quello che vale ...


7
Lo stesso vale per i costruttori beffardi, tra l'altro. Anche quelli non possono essere modificati tramite eredità.
Matthias

11
Può anche valere la pena aggiungere che alcuni sostenitori di TDD / TBD percepiscono la mancanza di un metodo statico e di derisione del costruttore come una buona cosa. Sostengono che quando ti ritrovi a dover deridere metodi o costruttori statici, questo è un indicatore di un design di classe scadente. Ad esempio, quando si segue un approccio IoC puristico nell'assemblaggio dei moduli di codice, non si avrà mai nemmeno la necessità di deridere statiche o ctor in primo luogo (a meno che non facciano parte di qualche componente della scatola nera, ovviamente). Vedi anche giorgiosironi.blogspot.com/2009/11/…
Matthias

211
Penso che gli strumenti di derisione dovrebbero darti ciò di cui hai bisogno senza dare per scontato che sappiano cosa è meglio per te. Ad esempio, se stavo usando una libreria di terze parti che utilizzava una chiamata al metodo statico che dovevo prendere in giro, sarebbe bello poterlo fare. L'idea che un framework fittizio non ti fornisca alcune capacità perché è visto come un cattivo design è fondamentalmente difettoso.
Lo-Tan

11
@ Lo-Tan - è come dire che una lingua dovrebbe essere capace di tutto, senza dare per scontato che sappia meglio di te. Questa è solo vanità da parte tua, perché risultano imponenti. Il problema qui è che la battaglia "anti / pro statica" non è chiara, e lo sono anche le strutture. Sono d'accordo che dovremmo avere entrambi. Ma dove i fatti sono chiari, preferisco un quadro che imponga quei fatti. Questo è un modo di apprendere: strumenti che ti tengono in carreggiata. Quindi tu stesso non devi. Ma ora ogni testa di tagliatella può imporre il suo cosiddetto "buon design". "Fondamentalmente imperfetto" ...
nevvermind

17
@nevvermind Eh? Un linguaggio di alto livello è pensato per aiutarti e avere le astrazioni necessarie in modo che tu possa concentrarti sui pezzi importanti. Una libreria di test è uno strumento, uno strumento che utilizzo per produrre codice di qualità migliore e, si spera, meglio progettato. Qual è il punto di una libreria di test / mock quando ha limitazioni che potrebbero significare che non posso usarla quando devo integrare il codice mal progettato di qualcun altro? Non sembra ben pensato, mentre le buone lingue lo sono state .
Lo-Tan

28

Se hai bisogno di deridere un metodo statico, è un forte indicatore di un cattivo design. Di solito, deridi la dipendenza della tua classe sottoposta a test. Se la tua classe sottoposta a test fa riferimento a un metodo statico, come java.util.Math # sin, ad esempio, significa che la classe sottoposta a test necessita esattamente di questa implementazione (ad esempio, precisione e velocità). Se vuoi astrarre da un'implementazione concreta del seno, probabilmente hai bisogno di un'interfaccia (vedi dove va a finire)?


3
Bene, ho usato metodi statici per fornire astrazioni di alto livello, come una "facciata di persistenza statica". Tale facciata tiene il codice client lontano dalle complessità e dai dettagli di basso livello di un'API ORM, fornendo un'API più coerente e facile da usare, consentendo al contempo molta flessibilità.
Rogério

E perché devi prenderlo in giro? Se dipendi dal metodo statico, "unità" o "modulo" non è la sola classe ma include anche la "facciata di persistenza statica".
Jan

91
Vero, ma a volte potresti non avere scelta se, ad esempio, hai bisogno di deridere un metodo statico che si trova in una classe di terze parti.
Stijn Geukens

7
Vero, ma a volte potremmo avere a che fare con single.
Manu Manjunath,

L'unica cosa che non può essere risolta con l'astrazione è troppi livelli di astrazione ... L'aggiunta di livelli di astrazione aggiunge complessità e spesso non è necessaria. Penso agli esempi che ho visto di framework che cercano di deridere System.currentTimeMillis () avvolgendo questa semplice chiamata in una singola classe. Finiamo con una classe singleton per metodo invece di avere semplicemente metodi, solo per facilitare i test. E poi quando introduci un dep di terze parti che chiama il metodo statico direttamente invece che attraverso il tuo wrapper singleton, i test falliscono comunque ...
Padre Jeremy Krieg

6

Mockito [3.4.0] può simulare metodi statici!

  1. Sostituisci mockito-coredipendenza con mockito-inline:3.4.0.

  2. Classe con metodo statico:

    class Buddy {
      static String name() {
        return "John";
      }
    }
    
  3. Usa un nuovo metodo Mockito.mockStatic():

    @Test
    void lookMomICanMockStaticMethods() {
      assertThat(Buddy.name()).isEqualTo("John");
    
      try (MockedStatic<Buddy> theMock = Mockito.mockStatic(Buddy.class)) {
        theMock.when(Buddy::name).thenReturn("Rafael");
        assertThat(Buddy.name()).isEqualTo("Rafael");
      }
    
      assertThat(Buddy.name()).isEqualTo("John");
    }
    

    Mockito sostituisce il metodo statico trysolo all'interno del blocco.


Per me una Testclass ha fornito alcune informazioni davvero utili su come utilizzare la nuova funzionalità statickMock : StaticMockTest.java (è abbastanza importante utilizzare il blocco try). Vedere anche le correzioni di bug nelle versioni 3.4.2 e 3.4.6 e per completezza il numero originale 1013 .
MichaelCkr

5

Penso seriamente che sia odore di codice se hai bisogno di deridere anche i metodi statici.

  • Metodi statici per accedere a funzionalità comuni? -> Usa un'istanza singleton e inseriscila
  • Codice di terze parti? -> Inseriscilo nella tua interfaccia / delegato (e se necessario rendilo anche singleton)

L'unica volta che questo mi sembra eccessivo, sono librerie come Guava, ma non dovresti aver bisogno di deridere questo tipo comunque perché fa parte della logica ... (cose come Iterables.transform (..)) In
questo modo il tuo codice rimane pulito, puoi deridere tutte le tue dipendenze in modo pulito e hai uno strato anti-corruzione contro le dipendenze esterne. Ho visto PowerMock in pratica e tutte le classi per cui avevamo bisogno erano mal progettate. Anche l'integrazione di PowerMock a volte ha causato seri problemi
(es. Https://code.google.com/p/powermock/issues/detail?id=355 )

PS: lo stesso vale anche per i metodi privati. Non credo che i test dovrebbero conoscere i dettagli dei metodi privati. Se una classe è così complessa da tentare di deridere i metodi privati, è probabilmente un segno per dividere quella classe ...


2
Singleton ti farà incorrere in tutti i tipi di problemi, in particolare quando ti rendi conto che in realtà hai bisogno di più di un'istanza e ora devi rifattorizzare l'intero sistema per farlo accadere.
Ricardo Freitas

Non ho detto che consiglio a tutti il ​​Singleton Pattern. Quello che volevo dire è che se devo decidere tra una classe di utilità statica e un Singleton che offre le stesse funzionalità, sceglierei Singleton. E se una classe è un Singleton o meno dovrebbe essere controllata comunque dal framework DI, nella mia classe I @Inject SomeDependencye nella mia configurazione lo definisco bind(SomeDependency.class).in(Singleton.class). Quindi se domani non è più un Singleton, cambio quella configurazione e il gioco è fatto.
pete83

@ pete83 Ti sento fratello. Tuttavia ho un problema con librerie di test o framework che richiedono agli sviluppatori di modificare il loro design per soddisfare i limiti / design del framework di test. Questo è IMO che mette il carro davanti ai buoi, o la coda che scodinzola il cane.
Matt Campbell

1
Questo argomento ha poco senso per me. I modelli singleton sono caduti in disgrazia da anni ormai, per troppe ragioni da elencare qui. Cosa costituisce un codice "pulito"? Se ho un metodo di istanza di classe che chiama un metodo di supporto statico che restituisce alcune operazioni di I / O, perché non dovrei volerlo deridere in un test? E com'è quel design scadente? Tutto questo tormentare le mani sui metodi statici beffardi non si somma. Deridere un metodo è l'opposto di testarlo. Se è troppo difficile da implementare, dillo e basta
eggmatters il

Oh amico, non stavo mai parlando di quel modello Singleton vecchia scuola in cui tutti chiamano Foo.getInstance()ovunque. Ho appena scritto singleton nella risposta per contrastare l'argomento "ma un metodo statico non richiede la creazione di molti oggetti wrapper". Anche concettualmente per me c'è poca differenza tra un metodo statico e un metodo di istanza su un singleton, solo che non puoi deridere questo collaboratore singleton. Ma singleton o no non è assolutamente il punto che stavo cercando di sottolineare, il punto è iniettare e deridere i collaboratori e non chiamare metodi statici se rende difficile il test.
pete83

4

Mockito restituisce oggetti ma static significa "livello di classe, non livello di oggetto" Quindi mockito darà un'eccezione al puntatore nullo per static.


0

In alcuni casi, i metodi statici possono essere difficili da testare, soprattutto se devono essere derisi, motivo per cui la maggior parte dei framework beffardi non li supporta. Ho trovato questo post sul blog molto utile per determinare come simulare metodi e classi statici.


1
Il deridere dei metodi statici è ancora più facile del deridere i metodi delle istanze (poiché non vi è alcuna istanza), quando si utilizza un'API di simulazione adeguata.
Rogério

È come rispondere alla domanda con la domanda stessa, motivo per cui è difficile farlo, a cui questa non è una risposta.
Matthias

41
Ho votato in meno perché il post del blog consiglia una soluzione alternativa costosa (refactoring del codice di produzione), piuttosto che risolvere effettivamente il problema di isolare una classe dai metodi statici che utilizza. IMO, uno strumento beffardo che fa veramente il lavoro non discriminerebbe metodi di alcun tipo; uno sviluppatore dovrebbe essere libero di decidere se l'uso di metodi statici è buono o cattivo in una data situazione, piuttosto che essere costretto a seguire un percorso.
Rogério

0

In aggiunta alla risposta di Gerold Broser , ecco un esempio di derisione di un metodo statico con argomenti:

class Buddy {
  static String addHello(String name) {
    return "Hello " + name;
  }
}

...

@Test
void testMockStaticMethods() {
  assertThat(Buddy.addHello("John")).isEqualTo("Hello John");

  try (MockedStatic<Buddy> theMock = Mockito.mockStatic(Buddy.class)) {
    theMock.when(() -> Buddy.addHello("John")).thenReturn("Guten Tag John");
    assertThat(Buddy.addHello("John")).isEqualTo("Guten Tag John");
  }

  assertThat(Buddy.addHello("John")).isEqualTo("Hello John");
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.