2016-01-13 14 views
6

aufgerufen werden. Ich habe den folgenden Test, bei dem ich überprüfen muss, ob alle Getter der Person-Klasse aufgerufen werden. Bis jetzt habe ich mockitos verify() verwendet, um sicherzustellen, dass jeder Getter aufgerufen wird. Gibt es eine Möglichkeit, dies durch Nachdenken zu tun? Es kann der Fall sein, dass der Person-Klasse ein neuer Getter hinzugefügt wird, aber der Test wird dies vermissen.Vergewissern Sie sich, dass alle Gettermethoden

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()???** 
     } 
    } 
} 

Antwort

5

Allgemeinen verspotten nicht die Klasse im Test . Wenn Ihr Test für eine Person ist, sollten Sie nie Mockito.mock(Person.class) darin sehen, da dies ein ziemlich klares Zeichen ist, dass Sie das spöttische Rahmenwerk anstelle des System-in-Test testen.

Stattdessen möchten Sie möglicherweise eine spy(new Person()) erstellen, die eine echte Person-Implementierung mit einem echten Konstruktor erstellt und dann ihre Daten in einen von Mockito generierten Proxy kopiert. Sie können MockingDetails.getInvocations() verwenden, um reflektiv zu überprüfen, dass jeder Getter aufgerufen wurde.

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

Es könnte ein Weg sein verify und invoke auf dem Mockito generierten Spion zu nennen, aber das scheint sehr zerbrechlich und sehr abhängig von Mockito Einbauten.

Nebenbei, testing Bean-Stil Getter scheint wie eine seltsame Verwendung von Zeit/Aufwand. Im Allgemeinen konzentrieren Sie sich auf das Testen von Implementierungen, die sich wahrscheinlich ändern oder abbrechen.

+0

Großartig! In Bezug auf Ihren letzten Rat, denke ich, der Test ist nicht für Person, sondern für eine Klasse wie PersonBuilder, PersonCloner oder etwas – Raffaele

+0

@Raffaele: True, aber hoffentlich die bessere Lösung gibt es tatsächlich zu überprüfen, dass jedes Feld kopiert wird, was bedeutet (aber nicht nötig) dass jeder getter aufgerufen wurde. –

+1

Dies funktionierte tatsächlich mit minimalem Aufwand für die Überprüfung der Aufruf aller Getter-Methoden. Einige Änderungen jedoch: 'BeanInfo beanInfo = Introspector.getBeanInfo (Person.class);' und hinzugefügt für 'setOfDescriptors' ein Filter mit:' .filter (p ->! P.contains ("getClass")) 'so dass diese Methode wird nicht berücksichtigt. –

0

Ich kann für Ihr Problem von zwei Lösungen denken:

  1. den Builder Code programmatisch generieren, so dass Sie keine Tests durchführen müssen. Java-Code wird von einem Programm generiert und niemals von einem Benutzer bearbeitet. Testen Sie stattdessen den Generator. Verwenden Sie eine Textvorlage und erstellen Sie Definitionen aus einem serialisierten Domänenmodell oder direkt aus Java kompilierten Klassen (Sie benötigen ein separates Modul abhängig von der Bean)

  2. Schreiben Sie Ihre Tests für eine Proxy-Bibliothek. Das Problem ist, dass reguläre Proxys nur Interfaces, keine regulären Klassen implementieren können, und es ist sehr umständlich, Interfaces für Javabeans zu haben. Wenn Sie diese Route wählen, würde ich mit Javassist gehen. Ich codierte eine lauffähige Probe und legte sie on GitHub. Die Testfälle verwenden einen Proxy-Fabrik Bohnen zu instanziiert (statt mit 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 
    } 
} 

Der Zähler wird in der Klasse gehalten MethodCallCounter

+0

Die Generierung des Builders für Personen und das anschließende Testen des Generators scheint eine Lösung zu sein. Der zweite mit den Proxy-Objekten sieht etwas komplizierter aus, aber ich würde es gerne ausprobieren und den Javassist ausprobieren. Danke für den Beispielcode! –

+0

Ich bevorzuge den Generatoransatz, aber entweder verwenden Sie eine andere Sprache als Java, um sowohl Beans als auch Builder zu definieren, oder Sie haben mindestens drei Kompilierungsmodule: die Beans, die Builder und die App – Raffaele

Verwandte Themen