2012-08-09 12 views
26

Mam funkcję, która wykorzystuje bieżący czas do wykonywania niektórych obliczeń. Chciałbym wyśmiać go za pomocą mockito.Jak wyśmiać nową datę() w java przy użyciu Mockito

Przykładem klasy Chciałbym przetestować:

public class ClassToTest { 
    public long getDoubleTime(){ 
     return new Date().getTime()*2; 
    } 
} 

Chciałbym coś takiego:

@Test 
public void testDoubleTime(){ 
    mockDateSomeHow(Date.class).when(getTime()).return(30); 
    assertEquals(60,new ClassToTest().getDoubleTime()); 
} 

to możliwe, aby drwić, że? Nie chciałbym zmieniać "przetestowanego" kodu w celu przetestowania.

+3

Dlaczego nie chcesz zmienić testowanego kodu? Kod, który jest bardziej sprawdzalny, jest generalnie luźniej powiązany ... dlaczego tego nie chcesz? – blank

+0

możliwy duplikat [Override Java System.currentTimeMillis] (http://stackoverflow.com/questions/2001671/override-java-system-currenttimemillis) –

+0

... i jeszcze coś - zmiana "przetestowanego" kodu jest łatwa - ty dostałem testy, aby powiedzieć, kiedy popełniłeś błąd - zmieniając nie testowany kod z drugiej strony ... potrzebujesz Michaela Feathersa kung foo;) – blank

Odpowiedz

39

Prawidłową rzeczą jest zrestrukturyzowanie kodu tak, aby był bardziej testowalny, jak pokazano poniżej. Restrukturyzacja swój kod, aby usunąć bezpośrednią zależność Data pozwoli Ci wstrzyknąć różne implementacje dla prawidłowego wykonywania i testowego środowiska wykonawczego:

interface DateTime { 
    Date getDate(); 
} 

class DateTimeImpl implements DateTime { 
    @Override 
    public Date getDate() { 
     return new Date(); 
    } 
} 

class MyClass { 

    private final DateTime dateTime; 
    // inject your Mock DateTime when testing other wise inject DateTimeImpl 

    public MyClass(final DateTime dateTime) { 
     this.dateTime = dateTime; 
    } 

    public long getDoubleTime(){ 
     return dateTime.getDate().getTime()*2; 
    } 
} 

public class MyClassTest { 
    private MyClass myClassTest; 

    @Before 
    public void setUp() { 
     final Date date = Mockito.mock(Date.class); 
     Mockito.when(date.getTime()).thenReturn(30L); 

     final DateTime dt = Mockito.mock(DateTime.class); 
     Mockito.when(dt.getDate()).thenReturn(date); 

     myClassTest = new MyClass(dt); 
    } 

    @Test 
    public void someTest() { 
     final long doubleTime = myClassTest.getDoubleTime(); 
     assertEquals(60, doubleTime); 
    } 
} 
+1

Zgadzam się. Robię to tak przez cały czas. Działa świetnie, zmiana oryginalnego kodu jest minimalna i testowanie jej jest łatwe. – stmax

+12

To jest klasyczny sposób rozwiązania tego problemu (to, co nazywacie 'DateTime' może bardziej opisowo nazywać się' Clock' lub coś podobnego). Jednak oznacza to restrukturyzację kodu i dodanie odrobiny złożoności wyłącznie w celu umożliwienia testowania, który jest nieco zakodowany przez kod. –

+0

Tak zrobiłem, myślę, że to dobry aproach, ale pytanie było jak to zrobić z Mockito: D –

6

Ty mógłby zrobić to za pomocą PowerMock, która zwiększa Mockito móc szydzić metody statyczne. Możesz wtedy mock System.currentTimeMillis(), od której ostatecznie otrzymasz czas.

Możesz może. Nie zamierzam wysuwać opinii, czy należy powinno być.

+1

Jestem zainteresowany wiedzą, jak robić takie rzeczy, to jest cel pytania. Czy masz przykłady? –

+0

To pochodzi z [Dokumentacja Powermock] (http://code.google.com/p/powermock/wiki/MockSystem). Powinien działać tak samo z PowerMockito – Brad

+1

Ślepe przekształcanie każdego "nowego" w obiekt otoki i wprowadzanie pośrednictwa przez wstrzykniętą zależność powoduje, że kod staje się niekoniecznie gadatliwy i trudny. Jeśli tworzyłeś ArrayList lub HashMap w swojej klasie, czy teraz utworzysz ArrayListFactory lub HashMapFactory i wstrzykniesz to do swojej klasy? Ślepe używanie PowerMocka wszędzie, gdzie używasz "nowego", może również stworzyć bardzo ściśle powiązany system. Możliwość określenia, gdzie narzędzia takie jak PowerMock dobrze pasują, jest częścią umiejętności programisty – Aneesh

14

Jeśli masz starszego kodu, który nie może byłaby i nie chcesz, aby wpłynąć System.currentTimeMillis(), spróbuj to za pomocą Powermock i PowerMockito

//note the static import 
import static org.powermock.api.mockito.PowerMockito.whenNew; 

@PrepareForTest({ LegacyClassA.class, LegacyClassB.class }) 

@Before 
public void setUp() throws Exception { 

    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 
    sdf.setTimeZone(TimeZone.getTimeZone("PST")); 

    Date NOW = sdf.parse("2015-05-23 00:00:00"); 

    // everytime we call new Date() inside a method of any class 
    // declared in @PrepareForTest we will get the NOW instance 
    whenNew(Date.class).withNoArguments().thenReturn(NOW); 

} 

public class LegacyClassA { 
    public Date getSomeDate() { 
    return new Date(); //returns NOW 
    } 
} 
1

Jedno podejście, które nie bezpośrednio odpowiedzieć na pytanie, ale może rozwiązać bazowego problem (posiadający powtarzalne testy), pozwala na Date jako parametr do testów i dodaje delegata do domyślnej daty.

Podobnie jak

public class ClassToTest { 

    public long getDoubleTime() { 
     return getDoubleTime(new Date()); 
    } 

    long getDoubleTime(Date date) { // package visibility for tests 
     return date.getTime() * 2; 
    } 
} 

W produkcji kodu, należy użyć getDoubleTime() i próby przed getDoubleTime(Date date).

Powiązane problemy