2015-10-19 53 views
4

類有一個類,看起來像這樣:我我怎麼能單元測試使用SimpleJdbcCall時

public class MyClass { 

    private final SimpleJdbcCall simpleJdbcCall; 

    public MyClass(final DataSource dataSource) { 
     this(new JdbcTemplate(dataSource)); 
    } 

    public MyClass(final JdbcTemplate template) { 
     simpleJdbcCall = new SimpleJdbcCall(template) 
      .withoutProcedureColumnMetaDataAccess() 
      .withCatalogName("MY_ORACLE_PACKAGE") 
      .withFunctionName("GET_VALUE") 
      .withReturnValue() 
      .declareParameters(
       new SqlOutParameter("RESULT", Types.VARCHAR)) 
      .declareParameters(
       new SqlParameter("P_VAR1_NAME", Types.VARCHAR)) 
      .declareParameters(
       new SqlParameter("P_VAR2_NAME", Types.VARCHAR)) 
      .useInParameterNames("P_VAR1_NAME", "P_VAR2_NAME"); 
    } 

    private String getValue(final String input) { 
     final SqlParameterSource params = new MapSqlParameterSource() 
      .addValue("P_VAR1_NAME", input, Types.VARCHAR) 
      .addValue("P_VAR2_NAME", null, Types.VARCHAR); 
     return simpleJdbcCall.executeFunction(String.class, params); 
    } 
} 

它正常工作,但我想爲它編寫單元測試和它的駕駛我瘋了。我試着嘲笑JdbcTemplate(Mockito),但是這會導致模擬連接,元數據等,並且我迷失於可調用聲明工廠的時間。

我想我可以編寫它,以便SimpleJdbcCall作爲參數傳遞給一個新的構造函數,然後嘲笑它,但那會讓人覺得駭人聽聞。除非是要改善它,否則我更喜歡這個測試不會影響課程。

我想繼續使用這個SimpleJdbcCall API。它爲我編寫SQL,所以我不必混合使用SQL和Java,但我也很想測試這個東西,而不必編寫1000行代碼。任何人都可以看到一個很好的方法來測試它嗎

回答

0

我肯定會採取添加構造函數的方法來允許直接注入SimpleJdbcCall

MyClass(SimpleJdbcCall simpleJdbcCall) { 
    this.simpleJdbcCall = simpleJdbcCall; 
} 

(並可能調用當前調用new的構造函數)。

這不是「hackish」,它只不過是Dependency Injection。我認爲在不需要測試SimpleJdbcCall的工作情況下讓課程可測試是一個明顯的改進。

在構造函數中調用new使得測試變得更加困難,因爲它是被實例化的類的緊密靜態耦合。

我發現Miško Hevery's blog post對這個話題很有意思。

+0

DI真的意味着配置的依賴,而不是一個不錯的選擇,以取代狀態的對象像'SimpleJdbcCall'。像這樣的依賴可以被嘲笑,所以代碼已經很容易測試。 –

+0

「這樣的依賴可以被嘲弄得很好」如何? (正版問題) –

+0

請注意,您可以在構造函數中保留'SimpleJdbcCall'的配置;只允許測試在外部創建實例,以便測試可以覆蓋'executeFunction()'。 –

0

我的第一個建議是將不是單元測試一下吧;寫一個集成測試,它實際執行Oracle數據庫中的存儲函數(但回滾事務)。

否則,您可以使用PowerMockito或JMockit模擬SimpleJdbcCall類,其中的代碼是按原樣測試的。

實施例的測試與JMockit:

@Mocked DataSource ds; 
@Mocked SimpleJdbcCall dbCall; 

@Test 
public void verifyDbCall() { 
    String value = new MyClass(ds).getValue("some input"); 

    // assert on value 

    new Verifications() {{ 
     SqlParameterSource params; 
     dbCall.executeFunction(String.class, params = withCapture()); 
     // JUnit asserts on `params` 
    }}; 
} 
+1

一般性評論:集成測試通常是一種代碼味道(「不知道他們在做什麼」) - 如果他們知道,他們可以編寫一個簡單,小而有效的單元測試。由於他們沒有,他們只是測試「東西」和很多它,但最終,沒有人能真正說出什麼是測試和什麼不是。 –

+0

我必須聲明對JMockit不熟悉:你的例子中的'dbCall'與'MyClass'有什麼關係? –

+1

@AaronDigulla這很荒唐。沒有人主張像你所說的那樣只進行*單元測試,因爲他們不擅長實際發現錯誤。普遍的共識,AFAIK,有*單元測試和某種綜合測試。儘管如此,我個人更喜歡只進行集成測試,因爲它在幾個項目中對我非常有用。 –

0

添加一個額外的構造:

/*test*/ MyClass(final SimpleJdbcCall call) { 
    simpleJdbcCall = call 
     .withoutProcedureColumnMetaDataAccess() 
     .withCatalogName("MY_ORACLE_PACKAGE") 
     .withFunctionName("GET_VALUE") 
     .withReturnValue() 
     .declareParameters(
      new SqlOutParameter("RESULT", Types.VARCHAR)) 
     .declareParameters(
      new SqlParameter("P_VAR1_NAME", Types.VARCHAR)) 
     .declareParameters(
      new SqlParameter("P_VAR2_NAME", Types.VARCHAR)) 
     .useInParameterNames("P_VAR1_NAME", "P_VAR2_NAME"); 
} 

這一個是包專用,因此其他類在同一包(=測試)可以調用它。這樣,測試可以創建一個覆蓋了executeFunction()的實例。您可以在方法中返回假結果或測試對象的狀態。

這意味着你的代碼仍然配置對象;測試只是通過了一個「POJO」,代碼被測試完成。

這樣,您不必編寫大量代碼 - 默認實現會爲您完成大部分工作。

或者,允許調用接口SimpleJdbcCallOperations的構造函數,這意味着您需要強大的模擬框架或編寫大量的鍋爐板代碼。

其他選擇:使用模擬JDBC驅動程序。這些通常很難建立,導致虛假的測試失敗,當測試失敗時,您通常不會真正知道爲什麼,...

或者內存數據庫。他們帶來了很多問題(您需要加載您需要製造和維護的測試數據)。

這就是爲什麼我儘量避免通過JDBC層往返的原因。假設JDBC和數據庫的工作原理 - 其他人已經測試過這個代碼。如果你再做一次,你只是在浪費你的時間。

相關:

0

我沒有使用它http://www.jmock.org/

XML配置 -

<bean id="simpleJDBCCall" class="org.springframework.jdbc.core.simple.SimpleJdbcCall"> 
    <property name="jdbcTemplate" ref="jdbcTemplate" /> 
</bean> 

Java文件 -

@Autowired 
private SimpleJdbcCall jdbcCall; 

測試類 -

simpleJDBCCall = mockingContext.mock(SimpleJdbcCall.class); 
mockingContext.checking(new Expectations() { 
     { 
      oneOf(simpleJDBCCall).withSchemaName("test"); 
      will(returnValue(simpleJDBCCall)); 
      oneOf(simpleJDBCCall).withCatalogName("test"); 
      will(returnValue(simpleJDBCCall)); 
      oneOf(simpleJDBCCall).withProcedureName(ProcedureNames.TEST); 
      will(returnValue(simpleJDBCCall)); 
      oneOf(simpleJDBCCall).execute(5); 
      will(returnValue(testMap)); 
     } 
相關問題