2014-12-23 25 views
5

Tôi có DynamoDBDao đơn giản sau đây có chứa một phương thức truy vấn một chỉ mục và trả về một bản đồ kết quả.Mockito: Mocking package private classes

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; 
    } 
} 

Tôi đang cố viết một bài kiểm tra đơn vị xác minh rằng khi chỉ mục DynamoDB trả về một ItemCollection thì Dao trả về bản đồ kết quả tương ứng.

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

Vấn đề của tôi là items.iterator() không thực sự trả về Iterator nó trả về IteratorSupport là gói riêng tư trong API tài liệu DynamoDB. Điều này có nghĩa là tôi không thể thực sự mô phỏng nó như tôi đã làm ở trên và vì vậy tôi không thể hoàn thành phần còn lại của bài kiểm tra của tôi.

Tôi có thể làm gì trong trường hợp này? Làm thế nào để đơn vị kiểm tra DAO của tôi một cách chính xác cho lớp tư nhân gói khủng khiếp này trong API tài liệu DynamoDB?

+1

chi tiết thi hành như tầm nhìn là một trong những lý do cho sự châm "[không giả loại bạn không sở hữu] (http : //www.davesquared.net/2011/04/dont-mock-types-you-dont-own.html) ". Bạn có thể viết một sự trừu tượng trên bất kỳ đối tượng nào trong số này với một hợp đồng/thực hiện mà bạn kiểm soát, hoặc mã chống lại một giao diện thay thế không? –

+0

Xin chào Jeff, cảm ơn bạn đã bình luận của bạn. Tôi không thấy làm thế nào tôi có thể viết một trừu tượng trên các đối tượng này với một hợp đồng/thực hiện tôi kiểm soát. Tôi đã cạn kiệt bộ công cụ hiện tại bị hạn chế bởi kiến ​​thức và kinh nghiệm của mình. Bạn có thể nhìn thấy một cái gì đó mà tôi hiện không thể? Nếu vậy thì tôi sẽ biết ơn nếu bạn có thể chỉ cho tôi đi đúng hướng. –

Trả lời

1

Dynamodb api có rất nhiều lớp như vậy mà không thể dễ dàng bị nhạo báng. Điều này dẫn đến rất nhiều thời gian dành cho việc viết các bài kiểm tra phức tạp và các tính năng thay đổi là nỗi đau lớn.

Tôi nghĩ rằng, đối với trường hợp này một cách tiếp cận tốt hơn sẽ được không cố gắng đi theo con đường truyền thống và sử dụng thư viện DynamodbLocal bởi nhóm AWS - http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Tools.DynamoDBLocal.html

này về cơ bản là một trong việc thực hiện ký ức về DyanamoDB. Chúng tôi đã viết các bài kiểm tra của mình như vậy trong quá trình khởi tạo thử nghiệm đơn vị, cá thể DyanmodbLocal sẽ được sinh ra và các bảng sẽ được tạo ra. Điều này làm cho việc kiểm tra trở nên dễ dàng. Chúng tôi chưa tìm thấy bất kỳ lỗi nào trong thư viện và nó được hỗ trợ và phát triển tích cực bởi AWS. Rất khuyên bạn nên nó.

5

Trước tiên, kiểm tra đơn vị không bao giờ nên cố gắng xác minh trạng thái riêng tư bên trong đối tượng. Nó có thể thay đổi. Nếu lớp học không thể hiện trạng thái của nó thông qua các phương thức getter không tư nhân, thì đó không phải là bài kiểm tra của bạn về cách triển khai nó.

Thứ hai, tại sao bạn quan tâm những gì thực hiện mà trình lặp có? Lớp đã hoàn thành hợp đồng của mình bằng cách trả về một trình lặp (một giao diện) mà khi được lặp lại sẽ trả về các đối tượng mà nó được yêu cầu.

Thứ ba, tại sao bạn chế nhạo các đối tượng mà bạn không cần? Xây dựng đầu vào và đầu ra cho các đối tượng giả mạo của bạn, đừng chế nhạo chúng; nó là không cần thiết. Bạn chuyển một Bảng vào hàm tạo của bạn? Khỏe.
Sau đó, mở rộng lớp Bảng để thực hiện bất kỳ phương thức được bảo vệ nào cho bất kỳ điều gì bạn cần. Thêm getters và/hoặc setters được bảo vệ vào lớp con Bảng của bạn. Yêu cầu họ trả về các giá trị mã hóa cứng nếu cần. Họ không quan trọng.

Hãy nhớ rằng, chỉ kiểm tra một lớp trong lớp kiểm tra của bạn. Bạn đang thử dao không phải bảng cũng như chỉ mục.

+0

Thật không may, các cuộc gọi dịch vụ đến DynamoDB cần phải được chế giễu và cuộc gọi dịch vụ chỉ có thể được truy cập bằng cách truy cập đa hình vào các lớp được bảo vệ gói, các loại Java thực thi khi chạy. Thay vì hỏi tại sao, bạn nên cung cấp một giải pháp. – Max

+0

Bạn đã thử giả lập trình vòng lặp được trả về bởi cuộc gọi ngụ ý trong vòng lặp for của bạn? Ghi đè ItemCollection để trả lại một số có thể lặp lại hoạt động như bạn muốn. –

+0

Đó không phải là Java hợp lệ. Java sẽ kiểm tra kiểu khi chạy. Một cá thể tùy ý của 'Iterator' không phải là một cá thể hợp lệ của' IteratorSupprt' để Java sẽ ném một ngoại lệ thời gian chạy. Tôi đã gửi một vấn đề về điều này với AWS và trình lặp không còn được bảo vệ gói: https://github.com/aws/aws-sdk-java/issues/465 – Max

0

Một workaround có thể là xác định một lớp thử nghiệm kéo dài IteratorSupport trong cùng một gói mà nó hiện diện trong, và xác định các hành vi mong muốn

Sau đó bạn có thể trở lại một thể hiện của lớp này thông qua thiết lập mô hình của bạn trong trường hợp thử nghiệm.

Tất nhiên, đây không phải là giải pháp tốt, nhưng chỉ đơn giản là giải pháp cho cùng một lý do mà @Jeff Bowman đã đề cập trong nhận xét.

0

Có thể tốt hơn là nên trích xuất ItemCollection về phương pháp riêng biệt không? Trong trường hợp của bạn có thể trông như sau:

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

sau đó trong các thử nghiệm đơn vị:

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

Khi bạn sử dụng when(items.iterator()).thenReturn(iterator); Mockito thấy các mặt hàng như ItemCollection mà gây ra lỗi biên dịch. Trong trường hợp thử nghiệm của bạn, bạn muốn xem ItemCollection chỉ là một Iterable. Vì vậy, giải pháp đơn giản là để đúc các mặt hàng như Iterable như dưới đây:

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

Ngoài ra hãy iterator của bạn như

@Mock 
Iterator<QueryOutcome> iterator; 

này cần khắc phục các mã mà không báo trước :)

Nếu bản sửa lỗi này vấn đề, xin vui lòng chấp nhận câu trả lời.

0

Bạn có thể kiểm tra phương pháp đọc của bạn bằng cách sử dụng đối tượng giả như thế này:

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; 
    } 
}