2015-02-27 6 views
5

Angenommen, Sie haben die folgende Klasse haben würden Sie Test mögen:Best Practice - ein Feld Einstellung ohne Setter in einem Unit-Test

public class SomeService { 
    public String someMethod(SomeEntity someEntity) { 
    return someEntity.getSomeProperty(); 
    } 
} 

Die SomeEntity sieht wie folgt aus:

public class SomeEntity { 
    private String someProperty; 

    public getSomeProperty() { 
    return this.someProperty; 
    } 
} 

Die Behauptung Sie möchten Folgendes tun:

String result = someService.someMethod(someEntity); 

assertThat(result).isEqualTo("someValue"); 

Wie können Sie diesen Test arbeiten lassen?

1) Fügen Sie einen Setter für 'someProperty' in der SomeEntity-Klasse hinzu. Ich denke nicht, dass dies eine gute Lösung ist, weil Sie den Produktionscode nicht ändern, damit Ihre Tests funktionieren.

2) Verwenden Sie ReflectionUtils, um den Wert dieses Felds festzulegen. Test würde wie folgt aussehen:

public class TestClass { 
     private SomeService someService; 

     @Test 
      public void testSomeProperty() { 
      SomeEntity someEntity = new SomeEntity(); 
      ReflectionTestUtils.setField(someEntity, "someProperty", "someValue"); 

      String result = someService.someMethod(someEntity); 

      assertThat(result).isEqualTo("someValue"); 
      } 
} 

3) Sie eine innere Klasse in Ihrer Testklasse erstellen, die die SomeEntity-Klasse erweitert und fügen den Setter für dieses Feld. Damit dies jedoch funktioniert, müssen Sie auch die SomeEntity-Klasse ändern, da das Feld "geschützt" statt "privat" werden soll. Test-Klasse könnte wie folgt aussehen:

public class TestClass { 
    private SomeService someService; 

    @Test 
    public void testSomeProperty() { 
    SomeEntityWithSetters someEntity = new SomeEntityTestWithSetters(); 
    someEntity.setSomeProperty("someValue"); 

    String result = someService.someMethod(someEntity); 

    assertThat(result).isEqualTo("someValue"); 
    } 


    public class SomeEntityWithSetters extends SomeEntity { 
    public setSomeProperty(String someProperty) { 
    this.someProperty = someProperty; 
    } 
    } 
} 

4) Sie verwenden Mockito zu Mock SomeEntity. Scheint in Ordnung, wenn Sie nur eine Eigenschaft in der Klasse vortäuschen müssen, aber was ist, wenn Sie wie 10 Eigenschaften nachmachen müssen. Der Test könnte wie folgt aussehen:

public class TestClass { 
    private SomeService someService; 

    @Test 
    public void testSomeProperty() { 
    SomeEntity someEntity = mock(SomeEntity.class); 
    when(someEntity.getSomeProperty()).thenReturn("someValue"); 

    String result = someService.someMethod(someEntity); 

    assertThat(result).isEqualTo("someValue"); 
    } 
} 
+1

Wenn Klasse ClassToTest Doesent haben Konstruktor oder Setter .. Wie in prod sie bevölkern die Eigenschaft .. wieder mit Reflexion? –

+0

und ich denke, mit dem Junit testen Sie tatsächlich SomeService-Methode und nicht Classtotest. Wenn dies der Fall ist, können Sie ClasstoTest nachmachen. und das Testen von ClasstoTest wird separat durchgeführt und es sollte kein Mocking verwendet werden –

+0

In diesem Fall wurde die Eigenschaft von Hibernate festgelegt, es ist eine Datenbank Entity, Hibernate benötigt keine Setter dafür. – wvp

Antwort

0

Mit JUnit-Tests von SomeService.someMethod()

Alternative 1. soll dies nicht, da keine Notwendigkeit verwenden, um Unternehmen für das Schreiben junit zu ändern.

Alternative 2. kann verwendet werden.

Alternative 3. wieder gleiche a 3, keine Notwendigkeit, für nur Junit zu verlängern. Wie wäre es, wenn die Klasse nicht erweitert werden kann?

Alternative 4. ja, eine gute Option. Mockito wird aus dem gleichen Grund benutzt.

+0

Dies ist nur ein einfaches Beispiel, die someMethod() wird wahrscheinlich noch mehr Sachen machen, aber das ist nur eine Illustration des Problems – wvp

+0

ja, stimmt, und wenn someMethod Aufrufe an SomeEntity macht, die verspottet werden sollen. Auf diese Weise wird das someMethod getestet. Recht ? –

+0

Meiner Meinung nach sollte es in der Tat verspottet werden, wenn die someEntity eine Abhängigkeit von dieser Klasse ist, nicht wenn es ein Objekt ist, das Sie verwenden. Ein Service, ein Repository, ein Mapper, ... Dinge wie diese müssen in der Tat verspottet werden. – wvp

2

Sie können einen Setter mit dem Standardbereich (Paket privat) hinzufügen.

+0

Dies würde auch den Produktionscode ändern, ich möchte dies nicht tun, wenn möglich – wvp

+5

Testgesteuerte Entwicklung hat immer großen Einfluss auf die Art und Weise, wie Sie codieren. Oft verbessert dies den Code. Aber manchmal verletzt es das Verbergen von Informationen, um Informationen für Tests sichtbar zu machen. Sie können keine perfekte Testabdeckung und keine perfekten Informationen finden. – BetaRide

0

Was ist das für SomeService spezifische Verhalten/Vertrag, das testbar ist? Basierend auf Ihrem Skelett-Code gibt es wirklich keine. Es wirft entweder eine NPE auf eine fehlerhafte Eingabe oder gibt eine Zeichenfolge zurück, die je nach Hibernate-Magie null ist oder nicht. Nicht sicher, was Sie tatsächlich können Test.

0

Ich habe dieses Dilemma schon oft durchgemacht, eine schnelle Lösung ist es, das Feld, das Sie mock-Paket schützen möchten, zu machen oder einen geschützten Setter bereitzustellen. Natürlich werden beide den Produktionscode ändern.

Alternativ können Sie das Framework für die Abhängigkeitsinjektion berücksichtigen, z. B. Dagger.Im Folgenden wird ein Beispiel geben sie:

@Module 
class DripCoffeeModule { 
    @Provides Heater provideHeater(Executor executor) { 
    return new CpuHeater(executor); 
    } 
} 

Dieses JUnit-Test DripCoffeeModule die Bindung für Heizung mit einem Mock-Objekt von Mockito außer Kraft setzt. Der Mock wird in den CoffeeMaker und auch in den Test gespritzt.

public class CoffeeMakerTest { 
    @Inject CoffeeMaker coffeeMaker; 
    @Inject Heater heater; 

    @Before public void setUp() { 
    ObjectGraph.create(new TestModule()).inject(this); 
    } 

    @Module(
     includes = DripCoffeeModule.class, 
     injects = CoffeeMakerTest.class, 
     overrides = true 
) 
    static class TestModule { 
    @Provides @Singleton Heater provideHeater() { 
     return Mockito.mock(Heater.class); 
    } 
    } 

    @Test public void testHeaterIsTurnedOnAndThenOff() { 
    Mockito.when(heater.isHot()).thenReturn(true); 
    coffeeMaker.brew(); 
    Mockito.verify(heater, Mockito.times(1)).on(); 
    Mockito.verify(heater, Mockito.times(1)).off(); 
    } 
} 
5

können Sie den Wert mit Reflexion gesetzt. Es braucht keine Änderung des Produktionscodes.

ReflectionTestUtils.setField (YourClass.class, "fieldName", fieldValue);