2012-10-27 11 views
6

Próbuję użyć Mockito do wyśmiewania klasy typu "Czytnik". Pomyśl o czytniku strumienia danych, ma on metody odczytywania różnych typów danych i przesuwania wewnętrznego wskaźnika po każdym odczycie.Modułowe wywoływanie kolejnych wywołań metod za pomocą Mockito

public interface Reader { 
    int readInt(); 
    short readShort(); 
} 

Testowana klasa odczytuje różne struktury danych ze strumienia danych. Na przykład,

public class Somethings { 
    public List<Something> somethings; 

    public Somethings(Reader reader) { 
     somethings = new List<Something>(); 
     int count = reader.readInt(); 
     for (int i=0; i<count; i++) { 
      somethings.add(readSomething(reader)); 
     } 
    } 

    private Something readSomething(Reader reader) { 
     int a = reader.readInt(); 
     short b = reader.readShort(); 
     int c = reader.readInt(); 
     return new Something(a, b, c); 
    } 
} 

I wreszcie, mam Test:

public class SomethingsTest { 
    @Test 
    public void testSomethings() { 
     Reader reader = Mockito.mock(Reader.class); 

     readCount(reader, 2); 
     readSomething(reader, 1, 2, 3); 
     readSomething(reader, 4, 5, 6); 

     Somethings somethings = new Somethings(reader); 
     Assert.assertEqual(2, somethings.size()); 
     Assert.assertEquals(new Something(1, 2, 3), somethings.somethings.get(0)); 
     Assert.assertEquals(new Something(4, 5, 6), somethings.somethings.get(1)); 
    } 

    private void readCount(Reader reader, int count) { 
     when(reader.readInt()).thenReturn(count); 
    } 

    private void readSomething(Reader reader, int a, short b, int c) { 
     when(reader.readInt()).thenReturn(a); 
     when(reader.readShort()).thenReturn(b); 
     when(reader.readInt()).thenReturn(c); 
    } 
} 

Niestety, to nie zadziała. read.readInt() zawsze zwraca 6 dla każdego wywołania. I rozumiem, dlaczego to zwraca 6. To nie jest moje pytanie.

Są dwie opcje, które mogę naprawić, ale nie podoba mi się żadna z nich.

Pierwszą opcją byłoby coś takiego:

public class SomethingsTest { 
    @Test 
    public void testSomethings() { 
     Reader reader = Mockito.mock(Reader.class); 

     when(reader.readInt()) 
      .thenReturn(2) 
      .thenReturn(1) 
      .thenReturn(3) 
      .thenReturn(4) 
      .thenReturn(6); 
     when(reader.readShort()) 
      .thenReturn(2) 
      .thenReturn(5); 

     Somethings somethings = new Somethings(reader); 
     Assert.assertEqual(2, somethings.size()); 
     Assert.assertEquals(new Something(1, 2, 3), somethings.somethings.get(0)); 
     Assert.assertEquals(new Something(4, 5, 6), somethings.somethings.get(1)); 
    } 
} 

To powinno działać, ale to bardzo monolityczny i niechlujny. Trudno zobaczyć, który powrót jest dla jakiego kawałka tej struktury, ponieważ wszystkie są wymieszane, bez żadnej struktury.

Druga opcja można myślę, jest coś takiego:

public class SomethingsTest { 
    @Test 
    public void testSomethings() { 
     Reader reader = Mockito.mock(Reader.class); 

     NewOngoingStubbing readIntStub = when(reader.readInt()); 
     NewOngoingStubbing readShortStub = when(reader.readShort()); 

     readCount(readIntStub, 2); 
     readSomething(readIntStub, readShortStub, 1, 2, 3); 
     readSomething(readIntStub, readShortStub, 4, 5, 6); 

     Somethings somethings = new Somethings(reader); 
     Assert.assertEqual(2, somethings.size()); 
     Assert.assertEquals(new Something(1, 2, 3), somethings.somethings.get(0)); 
     Assert.assertEquals(new Something(4, 5, 6), somethings.somethings.get(1)); 
    } 

    private void readCount(NewOngoingStubbing readIntStub, int count) { 
     readIntStub.thenReturn(count); 
    } 

    private void readSomething(NewOngoingStubbing readIntStub, 
      NewOngoingStubbing readShortStub, int a, short b, int c) { 
     readIntStub.thenReturn(a); 
     readShortStub.thenReturn(b); 
     readIntStub.thenReturn(c); 
    } 
} 

Tak przynajmniej utrzymuje strukturę oryginału, ale mający zdać osobny obiekt dla każdej metody zadzwonić chcesz, aby na zgaszone obiekt to ... ugh.

Jaki byłby najczystszy sposób na wykonanie tego testu? Czy jest tu jakaś opcja, której tu brakuje? Jakie funkcje mogę wykorzystać? Właśnie zacząłem używać Mockito dziś wieczorem, więc mogłem bardzo dobrze czegoś przegapić.

Odpowiedz

1

Gdy standardowe metody Mockito nie zapewniają mechanizmu do symulacji zachowania, można uciec się do wdrożenia własnego Answer. To więcej pracy, ale zapewnia dodatkową elastyczność.

W zależności od Twoich konkretnych wymagań, możesz na przykład utworzyć Answer, który zwróci nowy element z listy liczb, niezależnie od typu żądania (int lub short). Zmienna readList może być elementem, do którego można uzyskać dostęp ze wszystkich funkcji używanych do konfigurowania wyników.

final List<Integer> readList = new ArrayList<>(); 
// ... Fill readList with answers 

Answer answerFromList = new Answer() { 
    Object answer(InvocationOnMock invocation) { 
     // Remove and return first element 
     return readList.remove(0); 
    } 
} 

when(reader.readInt()).thenAnswer(answerFromList); 
when(reader.readShort()).thenAnswer(answerFromList); 

Zauważ, że ten Answer jest bardzo podobny dostarczona ReturnElementsOf, więc można używać bezpośrednio, jak również.

5

Mockito robi trwające odgałęzienie natywnie. Pierwszym przykładem jest w porządku, ale to powinno również pracować:

when(reader.readInt()).thenReturn(2, 1, 3, 4, 6); 

Dokumentacja dla niego jest here.

Jeśli masz coś z wyjątkowo złożoną interakcją, możesz uruchomić własną klasę pośredniczącą. Może się okazać, że inicjowanie fałszywych danych za pomocą realistycznych danych, a następnie ich użycie, zapewnia wyraźniejszy przykład współpracy klas niż Mockito. Jeśli tak jest, postępuj zgodnie z konwencją. Mockito jest IMO najlepszym szyderstwem, ale czasami wciąż rozwijam swoje.

+0

Tak jak wspomniałem, wiem, że jedna z dwóch alternatyw, o których wspomniałem, zadziała, ale nie dbam o żadną z nich. Wystarczy zmienić serię .thenReturn (1). ThenReturn (2) na .thenReturn (1, 2) w żaden sposób nie poprawi jej struktury :) – JesusFreke

+0

Dlatego właśnie zaproponowałem, aby przetoczyć własne jako alternatywę. – Lunivore

2

Możesz utworzyć klasę, która sobie z tym poradzi, np.

private static class ReaderStubber { 
    private final Reader reader = Mockito.mock(Reader.class); 
    private final NewOngoingStubbing readIntStub = when(reader.readInt());; 
    private final NewOngoingStubbing readShortStub = when(reader.readShort());; 

    public Reader getReader() { 
     return reader; 
    }  

    private void readCount(int count) { 
     readIntStub.thenReturn(count); 
    } 

    private void readSomething(int a, short b, int c) { 
     readIntStub.thenReturn(a); 
     readShortStub.thenReturn(b); 
     readIntStub.thenReturn(c); 
    } 
} 

Ale pytanie brzmi: czy naprawdę trzeba to zrobić z Mockito? Nie wszystko powinno być kpiną. Być może wystarczy zaimplementowanie kodu pośredniczącego Reader do testu z niektórymi List<Integer> w środku jest lepszy.


(Edit) Ponadto, jeśli jest to możliwe, być może należy przeprojektować Reader aby powrócić niezmienne i pewne NewOngoingReading.Często (ale nie zawsze) rzeczy, które trudno przetestować, lepiej przeprojektować. Ponadto nie musisz zajmować się synchronizacją.

+0

Hmm, Dobry pomysł, chociaż masz punkt dotyczący tego, czy korzystanie z Mockito jest tego warte. Prawdopodobnie pójdę z czymś bardziej podobnym: private void readInt (int val) {readIntStub.thenReturn (val); }. – JesusFreke

Powiązane problemy