2014-12-23 12 views
5

Mam następujące proste DynamoDBDao, które zawiera jedną metodę, która zapytuje indeks i zwraca mapę wyników.Mockito: Wyśmiewanie prywatnych klas pakietów

import com.amazonaws.services.dynamodbv2.document.*; 

public class DynamoDBDao implements Dao{ 
    private Table table; 
    private Index regionIndex; 

    public DynamoDBDao(Table table) { 
     this.table = table; 
    } 

    @PostConstruct 
    void initialize(){ 
     this.regionIndex = table.getIndex(GSI_REGION_INDEX); 
    } 

    @Override 
    public Map<String, Long> read(String region) { 
     ItemCollection<QueryOutcome> items = regionIndex.query(ATTR_REGION, region); 
     Map<String, Long> results = new HashMap<>(); 
     for (Item item : items) { 
      String key = item.getString(PRIMARY_KEY); 
      long value = item.getLong(ATTR_VALUE); 
      results.put(key, value); 
     } 
     return results; 
    } 
} 

Próbuję napisać badanej jednostki, która sprawdza, gdy indeks DynamoDB zwraca ItemCollection wtedy Dao zwraca wyniki odpowiednią mapę.

public class DynamoDBDaoTest { 

    private String key1 = "key1"; 
    private String key2 = "key2"; 
    private String key3 = "key3"; 
    private Long value1 = 1l; 
    private Long value2 = 2l; 
    private Long value3 = 3l; 

    @InjectMocks 
    private DynamoDBDao dynamoDBDao; 

    @Mock 
    private Table table; 

    @Mock 
    private Index regionIndex; 

    @Mock 
    ItemCollection<QueryOutcome> items; 

    @Mock 
    Iterator iterator; 

    @Mock 
    private Item item1; 

    @Mock 
    private Item item2; 

    @Mock 
    private Item item3; 

    @Before 
    public void setUp() { 
     MockitoAnnotations.initMocks(this); 
     when(table.getIndex(DaoDynamo.GSI_REGION_INDEX)).thenReturn(regionIndex); 
     dynamoDBDao.initialize(); 

     when(item1.getString(anyString())).thenReturn(key1); 
     when(item1.getLong(anyString())).thenReturn(value1); 
     when(item2.getString(anyString())).thenReturn(key2); 
     when(item2.getLong(anyString())).thenReturn(value2); 
     when(item3.getString(anyString())).thenReturn(key3); 
     when(item3.getLong(anyString())).thenReturn(value3); 
    } 

    @Test 
    public void shouldReturnResultsMapWhenQueryReturnsItemCollection(){ 

     when(regionIndex.query(anyString(), anyString())).thenReturn(items); 
     when(items.iterator()).thenReturn(iterator); 
     when(iterator.hasNext()) 
       .thenReturn(true) 
       .thenReturn(true) 
       .thenReturn(true) 
       .thenReturn(false); 
     when(iterator.next()) 
       .thenReturn(item1) 
       .thenReturn(item2) 
       .thenReturn(item3); 

     Map<String, Long> results = soaDynamoDbDao.readAll("region"); 

     assertThat(results.size(), is(3)); 
     assertThat(results.get(key1), is(value1)); 
     assertThat(results.get(key2), is(value2)); 
     assertThat(results.get(key3), is(value3)); 
    } 
} 

Moim problemem jest to, że items.iterator() faktycznie nie powrócić Iterator zwróci IteratorSupport który jest pakiet prywatne klasa w dokumencie DynamoDB API. Oznacza to, że nie mogę tak naprawdę kpić z tego, co zrobiłem powyżej, więc nie mogę dokończyć reszty mojego testu.

Co mogę zrobić w tym przypadku? W jaki sposób mogę przetestować jednostkę DAO poprawnie, biorąc pod uwagę tę okropną klasę prywatną pakietu w interfejsie API DynamoDB?

+1

szczegóły implementacji jak widoczności są jednym z powodów, dla wytycznej „[Nie makiety typy nie są właścicielami] (http : //www.davesquared.net/2011/04/dont-mock-types-you-dont-own.html) ". Czy możesz napisać streszczenie do dowolnego z tych obiektów za pomocą kontraktu/implementacji, którą kontrolujesz, lub zamiast tego kodować interfejs? –

+0

Cześć Jeff, dziękuję za komentarz. Nie widzę sposobu, w jaki mogę napisać abstrakcję dla tych obiektów za pomocą kontraktu/implementacji, którą kontroluję. Wykorzystałem obecny zestaw narzędzi, który jest ograniczony moją wiedzą i doświadczeniem. Czy widzisz coś, czego obecnie nie mogę? Jeśli tak, byłbym wdzięczny, gdybyś wskazał mi właściwy kierunek. –

Odpowiedz

1

Dynamodb api ma wiele takich klas, których nie można łatwo wyśmiać. To powoduje, że wiele czasu spędzonego na pisaniu złożonych testów i zmieniających się funkcji to duży ból.

myślę, że w tym przypadku lepszym rozwiązaniem nie będzie próbować iść w tradycyjny sposób i korzystać z biblioteki DynamodbLocal przez zespół AWS - http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Tools.DynamoDBLocal.html

To jest w zasadzie w realizacji pamięci DyanamoDB. Napisaliśmy nasze testy, że podczas inicjowania testów jednostkowych, instancja DyanmodbLocal zostanie utworzona, a tabele zostaną utworzone. To sprawia, że ​​testowanie jest proste. Nie znaleźliśmy jeszcze żadnych błędów w bibliotece i jest ona aktywnie wspierana i rozwijana przez AWS. Gorąco go polecam.

5

Po pierwsze, testy jednostkowe nigdy nie powinny próbować weryfikować stanu prywatnego wewnątrz obiektu. Może się zmienić. Jeśli klasa nie ujawnia swojego stanu za pomocą nieprywatnych metod pozyskiwania, nie jest to żadna z firm testowych, w jaki sposób jest ona wdrożona.

Po drugie, dlaczego obchodzi Cię, jaką implementację ma iterator? Klasa spełniła swoją umowę, zwracając iterator (interfejs) , który po iteracji spowoduje zwrócenie obiektów, które ma.

Po trzecie, dlaczego kpisz z przedmiotów, których nie potrzebujesz? Skonstruuj wejścia i wyjścia dla wyśmianych obiektów, nie kpij z nich; to jest niepotrzebne. Możesz przekazać tabelę do swojego konstruktora? W porządku.
Następnie rozszerzyć klasę tabeli, aby tworzyć dowolne metody chronione, niezależnie od tego, czego potrzebujesz:. Dodaj chronione obiekty pobierające i/lub ustawiające do swojej podklasy tabeli. Niech w razie potrzeby zwrócą wartości zakodowane. Nie mają znaczenia.

Pamiętaj, tylko przetestuj jedną klasę w swojej klasie testowej. Testujesz dao nie tabelę ani indeks.

+0

Niestety, wywołanie usługi dla DynamoDB musi być kpiną, a wywołanie usługi może być dostępne tylko poprzez polimorficzny dostęp do klas chronionych pakietami, których typy wymuszają Java w czasie wykonywania. Zamiast pytać, dlaczego, powinieneś podać rozwiązanie. – Max

+0

Czy próbowałeś wyśmiać iterator zwrócony przez domniemane połączenie w pętli for? Przesłonię ItemCollection, aby zwrócić Iterable , który zachowuje się zgodnie z oczekiwaniami. –

+0

To nie jest poprawna Java. Java sprawdzi typ w czasie wykonywania. Dowolna instancja 'Iteratora' nie jest poprawną instancją' IteratorSupprt', więc Java rzuci wyjątek środowiska wykonawczego. Zrobiłem o tym problem z AWS i iterator nie jest już chroniony pakietami: https://github.com/aws/aws-sdk-java/issues/465 – Max

0

Możliwym rozwiązaniem jest zdefiniowanie klasy testowej, która rozciąga IteratorSupport w tym samym opakowaniu, że jest obecny w, i określają pożądane zachowanie

Następnie można powrócić instancję tej klasy za pośrednictwem swojej makiety instalacji w przypadek testowy.

Oczywiście to nie jest dobre rozwiązanie, ale po prostu obejście tego problemu z tych samych powodów, o których w komentarzu wspomniał @Jeff Bowman.

0

Może lepiej wydobyć pobieranie ItemCollection do oddzielnej metody? W twoim przypadku może to wyglądać następująco:

public class DynamoDBDao { 

    protected Iterable<Item> readItems(String region) { // can be overridden/mocked in unit tests 
    // ItemCollection implements Iterable, since ItemCollection-specific methods are not used in the DAO we can return it as Iterable instance 
    return regionIndex.query(ATTR_REGION, region); 
    } 
} 

następnie w testach jednostkowych:

private List<Item> mockItems = new ArrayList<>(); // so you can set these items in your test method 

private DynamoDBDao dao = new DynamoDBDao(table) { 
    @Override 
    protected Iterable<Item> readItems(String region) { 
    return mockItems; 
    } 
} 
0

Podczas korzystania when(items.iterator()).thenReturn(iterator); Mockito widzi przedmioty jak ItemCollection który powoduje błąd kompilacji. W twoim przypadku testowym chcesz, aby ItemCollection był tylko Iterable. Tak, prostym rozwiązaniem jest oddać przedmioty jak iterowalny jak poniżej:

when(((Iterable<QueryOutcome>)items).iterator()).thenReturn(iterator); 

dokonać także swój iterator jako

@Mock 
Iterator<QueryOutcome> iterator; 

To powinno rozwiązać ten kod bez ostrzeżenia :)

jeśli ten poprawek problem, proszę przyjąć odpowiedź.

0

można przetestować metodę odczytu za pomocą fałszywych obiektów tak:

public class DynamoDBDaoTest { 

@Mock 
private Table table; 

@Mock 
private Index regionIndex; 


@InjectMocks 
private DynamoDBDao dynamoDBDao; 

public DynamoDBDaoTest() { 
} 

@Before 
public void setUp() { 
    MockitoAnnotations.initMocks(this); 
    when(table.getIndex(GSI_REGION_INDEX)).thenReturn(regionIndex); 
    dynamoDBDao.initialize(); 
} 

@Test 
public void shouldReturnResultsMapWhenQueryReturnsItemCollection() { 
    when(regionIndex.query(anyString(), anyString())).thenReturn(new FakeItemCollection()); 
    final Map<String, Long> results = dynamoDBDao.read("region"); 
    assertThat(results, allOf(hasEntry("key1", 1l), hasEntry("key2", 2l), hasEntry("key3", 3l))); 
} 

private static class FakeItemCollection extends ItemCollection<QueryOutcome> { 
    @Override 
    public Page<Item, QueryOutcome> firstPage() { 
     return new FakePage(); 
    } 
    @Override 
    public Integer getMaxResultSize() { 
     return null; 
    } 
} 

private static class FakePage extends Page<Item, QueryOutcome> { 
    private final static List<Item> items = new ArrayList<Item>(); 

    public FakePage() { 
     super(items, new QueryOutcome(new QueryResult())); 

     final Item item1= new Item(); 
     item1.with(PRIMARY_KEY, "key1"); 
     item1.withLong(ATTR_VALUE, 1l); 
     items.add(item1); 

     final Item item2 = new Item(); 
     item2.with(PRIMARY_KEY, "key2"); 
     item2.withLong(ATTR_VALUE, 2l); 
     items.add(item2); 

     final Item item3 = new Item(); 
     item3.with(PRIMARY_KEY, "key3"); 
     item3.withLong(ATTR_VALUE, 3l); 
     items.add(item3); 
    } 

    @Override 
    public boolean hasNextPage() { 
     return false; 
    } 

    @Override 
    public Page<Item, QueryOutcome> nextPage() { 
     return null; 
    } 
}