2009-07-17 50 views
1

我正在用mockito測試一個簡單的DAO層,但是我發現一個問題,基本上是一個難以測試的界面,我想知道您是否可以給我一些洞見...需要重構才能提高可測試性

這是我要測試的方法:

public Person getById(UserId id) { 
    final Person person = new PersonImpl(); 

    gateway.executeQuery(GET_SQL + id.getUserId(), new ResultSetCommand(){ 
     public int work(ResultSet rs) throws SQLException { 
     if(rs.next()){ 
      person.getName().setGivenName(rs.getString("name")); 
      person.getName().setFamilyName(rs.getString("last_name")); 
     } 
     return 0; 
     } 
    }); 
    return person; 
    } 

我使用DatabaseGateway這是我的Java代碼和SQL之間的接口,並且該方法接受一個匿名類,這是網關的方法的executeQuery:

public int executeQuery(String sql, ResultSetCommand cmd) { 
    try{ 
     Connection cn = createConnection(); 
     PreparedStatement st = cn.prepareStatement(sql); 
     int result = cmd.work(st.executeQuery()); 
     cn.close(); 
     return result; 
    }catch(Exception e){ 
     throw new RuntimeException("Cannot Create Statement for sql " + sql,e); 
    } 
    } 

問題在於,由於那個匿名類,測試PersonDAO變得越來越困難。

我可以重構整個代碼,甚至可以刪除匿名類,如果有人提出更好的設計(我敢肯定有更簡單的一個,但我似乎無法找到它)。

謝謝大家的建議。

PD:如果您需要了解更多信息,請隨時問


編輯:測試這是很難做到的

public void testGetPersonById(){ 
    DatabaseGateway gateway = mock(DatabaseGateway.class); 
    when(gateway.executeQuery(anyString(),any(ResultSetCommand.class))); 
    PersonDAO person_dao = new PersonDAOImpl(gateway); 

    Person p = person_dao.getById(new UserId(Type.viewer,"100")); 
    } 

看到了嗎? ResultCommand是模擬的一部分,我也對測試代碼感興趣...我應該對該特定命令做一個單獨的測試嗎?

+0

請給出一個越來越難做的測試的例子。 – 2009-07-17 14:24:35

+0

我不太瞭解Java,但我會說ResultSetCommand不相關。您正在測試getPersonById,以確保在給定有效的UserId時返回正確的Person,在給定無效的時候拋出異常等。如果它正常工作,則不關心它使用特定的ResultSetCommand。 – 2009-07-17 14:35:39

回答

1

而不是使用匿名類,你可以分別創建一個接口和它的實現。然後executeQuery方法將會有一個String和這個接口作爲參數。

所以你的測試將保持不變。你將能夠在另一個測試中對工作方法進行分離(對你的接口實現進行測試),這看起來很難測試。

結果會是這樣的:

public Person getById(UserId id) { 
    final Person person = new PersonImpl(); 

    gateway.executeQuery(GET_SQL + id.getUserId(), new MyInterfaceImpl(person)); 
    return person; 
} 

public int executeQuery(String sql, MyInterface cmd) { 
    try{ 
     Connection cn = createConnection(); 
     PreparedStatement st = cn.prepareStatement(sql); 
     int result = cmd.work(st.executeQuery()); 
     cn.close(); 
     return result; 
    }catch(Exception e){ 
     throw new RuntimeException("Cannot Create Statement for sql " + sql,e); 
    } 
    } 
0

你可以讓幻想和 「捕獲」 ResultSetCommand ARG,然後模擬回調就可以用模擬ResultSet

/** 
* Custom matcher - always returns true, but captures the 
* ResultSetCommand param 
*/ 
class CaptureArg extends ArgumentMatcher<ResultSetCommand> { 
    ResultSetCommand resultSetCommand; 
    public boolean matches(Object resultSetCommand) { 
     resultSetCommand = resultSetCommand; 
     return true; 
    } 
} 

public void testGetPersonById(){ 
    // setup expectations... 
    final String lastName = "Smith"; 
    final String firstName = "John"; 
    final CaptureArg captureArg = new CaptureArg(); 
    DatabaseGateway gateway = mock(DatabaseGateway.class); 
    ResultSet mockResultSet = mock(ResultSet.class); 
    when(gateway.executeQuery(anyString(), argThat(captureArg))); 
    when(mockResultSet.next()).thenReturn(Boolean.True); 
    when(mockResultSet.getString("name")).thenReturn(firstName); 
    when(mockResultSet.getString("last_name")).thenReturn(lastName); 

    // run the test... 
    PersonDAO person_dao = new PersonDAOImpl(gateway); 
    Person p = person_dao.getById(new UserId(Type.viewer,"100")); 

    // simulate the callback... 
    captureArg.resultSetCommand.work(mockResultSet); 

    // verify 
    assertEquals(firstName, person.getName().getGivenName()); 
    assertEquals(lastName, person.getName().getFamilyName()); 
} 

我很矛盾,我是否喜歡這個 - 我t暴露了很多正在測試的方法的內部結構。但至少這是一個選擇。