2009-05-18 3 views
21

결과 집합을 조롱하고 싶습니다. 진지하게. ResultSet에서 데이터를 구문 분석하는 하나의 복잡한 복잡한 코드를 리펙토링하고 있으며, 코드가 동일하게 동작하기를 바랍니다. 그래서, 이것을 테스트 할 수 있도록 리팩토링되는 조각에 대한 단위 테스트를 작성해야합니다. 조롱 순서를 looooong 쓰기,데이터로 ResultSet을 채우는 쉬운 방법

  1. 사용 EasyMock에 :

    인터넷 검색 후 나는이 개 아이디어를 내놓았다. 매우 나쁜 솔루션 : 초기 데이터를 추가하기 어렵고, 데이터를 변경하기 어렵고, 큰 테스트 디버깅 약속입니다.

  2. Apache Derby 또는 HSQLDB를 사용하여 메모리 내 DB를 작성하고 파일 또는 문자열 배열에서 채우고 마법의 InMemoryDBUtils.query (SQL)를 쿼리하십시오. 그런 다음 해당 ResultSet을 사용하십시오. 불행히도 필자는 테스트를 신속하게 작성하는 마법적인 InMemoryDBUtils를 찾지 못했습니다. IBM 기사 "Derby로 지속성을 분리 한 단위 테스트"는 필자가 필요로하는 것만 큼 좋을 것 같습니다 ...

두 번째 방법은 다소 쉽고 훨씬 지원 가능합니다.

그런 모의를 만들면 어떤 조언을 해주겠습니까? (의사에도 불구하고, 물론 :-)? 나는 놓치고있다 눈썹 약간의 총알? 아마도 DBUnit이이를위한 도구일까요?

답변

11

DBUnit은 메모리 데이터베이스를 채우는 데 도움이되지만 내 지식으로 결과 세트를 제공하지 않습니다.

나는이 시점에서 조롱 프레임 워크가 잘못된 접근이라고 말합니다. 조롱은 단순히 데이터를 반환하는 것이 아니라 행동과 상호 작용을 테스트하는 것이므로 사용자의 의도대로 진행될 것입니다.

대신 결과 세트 인터페이스를 구현하거나 전체 결과 세트를 구현하지 않고도 관심있는 메소드를 구현하는 클래스에 대한 결과 세트 인터페이스의 동적 프록시를 작성합니다. 메모리 데이터베이스를 유지하는 것만 큼 쉽게 클래스를 유지 관리 할 수 ​​있습니다 (테스트중인 데이터 집합이 일관성이 있다면). 디버깅이 더 쉬울 것입니다.

dbunit을 사용하여 결과 집합의 스냅 샷을 찍은 DBUnit을 사용하여 해당 클래스를 백업하고 dbunit에서 테스트 중에 xml에서 다시 읽게하고 더미 결과 집합에서 dbunit 클래스의 데이터를 읽게 할 수 있습니다. 데이터가 약간 복잡하다면 합리적인 방법이 될 것입니다.

클래스가 이렇게 결합되어 동일한 테스트의 일부로 수정 된 데이터를 읽어야 할 경우 메모리 데이터베이스를 가져갈 것입니다. 그럼에도 불구하고, 나는 당신이 그 의존성을 떼어 낼 때까지 실제 데이터베이스의 사본을 사용하는 것을 고려할 것이다.

간단한 프록시 생성 방법 :

private static class SimpleInvocationHandler implements InvocationHandler { 
    private Object invokee; 

    public SimpleInvocationHandler(Object invokee) { 
     this.invokee = invokee; 
    } 

    public Object invoke(Object proxy, Method method, Object[] args) 
      throws Throwable { 
     method = invokee.getClass().getMethod(method.getName(), method.getParameterTypes()); 
     if (!method.isAccessible()) { 
      method.setAccessible(true); 
     } 
     try { 
      return method.invoke(invokee, args); 
     } catch (InvocationTargetException e) { 
      throw e.getTargetException(); 
     } 
    } 
} 

public static <T> T generateProxy(Object realObject, Class... interfaces) { 
    return (T) Proxy.newProxyInstance(realObject.getClass().getClassLoader(), interfaces, new SimpleInvocationHandler(realObject)); 
} 
+0

안녕하세요. Yishai 님, 의견을 보내 주셔서 감사합니다. 필자는 10 개 이상의 테스트 각각에 대해 약 100 개의 레코드를 조롱하며 기록은 10 개의 필드로 구성됩니다. 이 AFAIU는 파일 및 사용자 정의 ResultSet 구현에 데이터를 저장하기 위해 DBUnit을 남겨 둡니다. 응답 해 주셔서 다시 한 번 감사드립니다. – DiaWorD

+0

당신은 오신 것을 환영합니다. 나는 각각의 10 가지 테스트가 각각 다른 데이터를 필요로한다면 그렇다고 합리적 접근법이라고 말할 수 있습니다. DBUnit을 사용하면 ResultSet을 가져 와서 XML에 쓸 수 있으므로 테스트에서 참조 할 수 있습니다. – Yishai

2

해당되는 경우 실제 데이터 소스의 결과 세트를 가져 와서 직렬화하고 파일을 저장할 수 있습니다. 그런 다음 각 단위 테스트에 대해 결과 집합을 deserialize 할 수 있습니다. 그러면 좋은 결과를 얻을 수 있습니다.

+0

안녕하세요. GWLlosa 및 의견을 보내 주셔서 감사합니다. DBUnit이 파일에 데이터를 저장하는 데 도움이 될 것이라고 생각합니다. – DiaWorD

1

대부분 ResultSet 메서드를 호출하지 않는 한, 나는 아마 2 차원 배열에 구분 된 텍스트 파일을로드하고 실제로 필요한 메서드를 구현하고 나머지는 UnsupportedOperationException (내 IDE에서 stubbed-out 메서드의 기본 구현입니다).

36

여기에서 MockResultSet 클래스 성공을 했어 : http://mockrunner.sourceforge.net/합니다. ResultSet 인터페이스를 구현하는 클래스를 만들 수 있으며 각 열과 행의 값을 설정할 수 있습니다.

합리적인 크기의 ResultSet을 사용하는 메서드가 있으면 비교적 쉽게 필요한 값을 반환하는 테스트를 만들 수 있습니다.

MockResultSet rs = new MockResultSet("myMock"); 

rs.addColumn("columnA", new Integer[]{1}); 
rs.addColumn("columnB", new String[]{"Column B Value"}); 
rs.addColumn("columnC", new Double[]{2}); 

// make sure to move the cursor to the first row 
try 
{ 
    rs.next(); 
} 
catch (SQLException sqle) 
{ 
    fail("unable to move resultSet"); 
} 

// process the result set 
MyObject obj = processor.processResultSet(rs); 

// run your tests using the ResultSet like you normally would 
assertEquals(1, obj.getColumnAValue()); 
assertEquals("Column B Value", obj.getColumnBValue()); 
assertEquals(2.0d, obj.getColumnCValue()); 
+0

안녕하세요. mjd79, 답변 해 주셔서 감사합니다. 필자의 경우 문제는 각 테스트 케이스마다 약 100 개의 레코드가 있고 약 10 개의 테스트 케이스 :-)가 있습니다. 각 레코드에는 날짜, 숫자 및 문자열을 포함한 10 개의 필드가 있습니다. 그렇기 때문에 모의 (mock) 데이터를 파일에 저장하는 것이 좋으며, 거의 모든 DB 클라이언트에서 직접 가져올 수있는 INSERT INTO 양식을 선호합니다. 모크 러너 (MockRunner)는 아주 좋아 보이지만 다른 경우에는 그대로 두겠습니다. 시간을 내 주셔서 다시 한 번 감사드립니다. – DiaWorD

+0

mockrunner는 일괄 테스트를 위해 간단한 결과 집합을 조롱하는 훌륭한 리소스였습니다. 무리 감사! – EdgeCaseBerg

+0

이것은 허용 된 대답이어야합니다. – JonyD

5

Mockrunner는 CSV 또는 XML 파일을로드하고 자동으로 MockResultSet를 만들 수 있습니다

다음은 간단한 예입니다. 또한 Connection 및 Statement를 모의 할 수 있으므로 JDBC 드라이버를 클래스 경로에 추가하지 않고도 모든 JDBC 항목이 간단하게 작동합니다.

5

나는이 경우에 대해 뭔가를 작성했습니다. Mockito를 사용하여 결과 집합을 조롱 할 수 있습니다. resultet.next()를이 코드와 조롱하여 결과 집합의 모의 행을 반복 할 수도 있습니다.

// two dimensional array mocking the rows of database. 
String[][] result = { { "column1", "column2" }, { "column1", "column2" } }; 

@InjectMocks 
@Spy 
private TestableClass testableClass; 

@Mock 
private Connection connection; 

@Mock 
private Statement statement; 

@Mock 
private ResultSet resultSet; 

@BeforeTest 
public void beforeTest() { 
    MockitoAnnotations.initMocks(this); 
} 

@BeforeMethod 
public void beforeMethod() throws SQLException { 
    doAnswer(new Answer<Connection>() { 
     public Connection answer(InvocationOnMock invocation) 
       throws Throwable { 
      return connection; 

     } 
    }).when(testableClass).getConnection(); 

    when(connection.createStatement()).thenReturn(statement); 
    when(statement.executeQuery(anyString())).thenReturn(resultSet); 
    final AtomicInteger idx = new AtomicInteger(0); 
    final MockRow row = new MockRow(); 

    doAnswer(new Answer<Boolean>() { 

     @Override 
     public Boolean answer(InvocationOnMock invocation) throws Throwable { 
      int index = idx.getAndIncrement(); 
      if (result.length > index) { 
       String[] current = result[index]; 
       row.setCurrentRowData(current); 
       return true; 
      } else 
       return false; 

     } 

     ; 
    }).when(resultSet).next(); 

    doAnswer(new Answer<String>() { 

     @Override 
     public String answer(InvocationOnMock invocation) throws Throwable { 
      Object[] args = invocation.getArguments(); 
      int idx = ((Integer) args[0]).intValue(); 
      return row.getColumn(idx); 
     } 

     ; 
    }).when(resultSet).getString(anyInt()); 
} 

static class MockRow { 
    String[] rowData; 

    public void setCurrentRowData(String[] rowData) { 
     this.rowData = rowData; 
    } 

    public String getColumn(int idx) { 
     return rowData[idx - 1]; 
    } 
} 
관련 문제