2016-01-13 17 views
6

Ho il seguente test in cui ho bisogno di verificare che tutti i getter della classe Person vengano chiamati. Finora ho usato mockito's verify() per assicurarmi che ogni getter venga chiamato. C'è un modo per farlo con la riflessione? Può succedere che un nuovo getter venga aggiunto alla classe Person ma il test mancherà questo.Verificare che tutti i metodi getter siano chiamati

public class GetterTest { 
    class Person{ 

     private String firstname; 
     private String lastname; 

     public String getFirstname() { 
      return firstname; 
     } 

     public String getLastname() { 
      return lastname; 
     } 
    } 

    @Test 
    public void testAllGettersCalled() throws IntrospectionException{ 
     Person personMock = mock(Person.class); 
     personMock.getFirstname(); 
     personMock.getLastname(); 

     for(PropertyDescriptor property : Introspector.getBeanInfo(Person.class).getPropertyDescriptors()) { 
      verify(personMock, atLeast(1)).getFirstname(); 
      //**How to verify against any getter method and not just getFirstName()???** 
     } 
    } 
} 

risposta

5

In generale, non deridere la classe in prova . Se il test è per una persona, non dovresti mai vedere Mockito.mock(Person.class) al suo interno, poiché è un chiaro segno che stai testando il framework di simulazione anziché il sistema sotto test.

Invece, è possibile creare uno spy(new Person()), che creerà un'implementazione di Persona reale utilizzando un vero costruttore e quindi copierà i suoi dati su un proxy generato da Mockito. È possibile utilizzare MockingDetails.getInvocations() per verificare in modo riflessivo che sia stato chiamato ogni agente.

// This code is untested, but should get the point across. Edits welcome. 
// 2016-01-20: Integrated feedback from Georgios Stathis. Thanks Georgios! 

@Test 
public void callAllGetters() throws Exception { 
    Person personSpy = spy(new Person()); 
    personSpy.getFirstname(); 
    personSpy.getLastname(); 

    assertAllGettersCalled(personSpy, Person.class); 
} 

private static void assertAllGettersCalled(Object spy, Class<?> clazz) { 
    BeanInfo beanInfo = Introspector.getBeanInfo(clazz); 
    Set<Method> setOfDescriptors = beanInfo.getPropertyDescriptors() 
     .stream() 
     .map(PropertyDescriptor::getReadMethod) 
     .filter(p -> !p.getName().contains("getClass")) 
     .collect(Collectors.toSet()); 
    MockingDetails details = Mockito.mockingDetails(spy); 
    Set<Method> setOfTestedMethods = details.getInvocations() 
     .stream() 
     .map(InvocationOnMock::getMethod) 
     .collect(Collectors.toSet()); 
    setOfDescriptors.removeAll(setOfTestedMethods); 
    // The only remaining descriptors are untested. 
    assertThat(setOfDescriptors).isEmpty(); 
} 

Ci potrebbe essere un modo per chiamare verify e invoke sulla spia Mockito generati, ma che sembra molto fragile, e molto dipende da interni Mockito.

Per inciso, testare i getter in stile bean sembra uno strano uso di tempo/sforzo. In generale, concentrarsi sulle implementazioni di test che potrebbero cambiare o rompersi.

+0

Great! Per quanto riguarda il tuo ultimo consiglio, penso che il test non sia per Person ma per qualche classe come PersonBuilder, PersonCloner o qualcosa di simile – Raffaele

+0

@Raffaele: Vero, ma si spera che la soluzione migliore sia controllare effettivamente che ogni campo sia copiato, il che implica (ma non non richiedere) che ogni getter è stato chiamato. –

+1

Questo ha funzionato con il minimo sforzo per verificare la chiamata di tutti i metodi getter. Alcune modifiche però: 'BeanInfo beanInfo = Introspector.getBeanInfo (Person.class);' e aggiunto per 'setOfDescriptors' un filtro con:' .filter (p ->! P.contains ("getClass")) 'in modo che questo metodo non è preso in considerazione. –

0

Mi vengono in mente due soluzioni per il vostro problema:

  1. generare il codice Builder a livello di codice, quindi non è necessario eseguire i test. Il codice Java è generato da un programma e mai modificato da un utente. Prova invece il generatore. Utilizzare un modello di testo e creare definizioni da un modello di dominio serializzato o direttamente da classi compilate Java (è necessario un modulo separato dipendente da quello del bean)

  2. Scrivere i test su una libreria proxy. Il problema è che i proxy regolari possono solo implementare interfacce, non classi regolari, ed è molto complicato avere interfacce per Javabeans. Se scegli questa strada, andrei con Javassist. Ho codificato un campione eseguibile e lo metto on GitHub. I casi di test usano una fabbrica di proxy per istanziare i fagioli (invece di utilizzare new)

public class CountingCallsProxyFactory { 

    public <T> T proxy(Class<T> classToProxy) { 
     ProxyFactory factory = new ProxyFactory(); 
     factory.setSuperclass(classToProxy); 
     Class clazz = factory.createClass(); 
     T instance = (T) clazz.newInstance(); 
     ProxyObject proxy = (ProxyObject) instance; 
     MethodCallCounter handler = new MethodCallCounter(); 
     proxy.setHandler(handler); 
     return instance; 
    } 

    public void verifyAllGettersCalled(Object bean) { 
     // Query the counter against the properties in the bean 
    } 
} 

Il contatore viene mantenuto all'interno della classe MethodCallCounter

+0

Generare il generatore Person e quindi testare il generatore sembra una soluzione. Il secondo con gli oggetti proxy sembra un po 'più complicato, ma mi piacerebbe provarlo e provare Javassist. Grazie per il codice di esempio! –

+0

Preferisco l'approccio generatore, ma di nuovo o usi un linguaggio diverso da Java per definire sia i bean che i builder, o avrai almeno tre moduli di compilazione: i bean, i builder e l'app – Raffaele

Problemi correlati