我有一个看起来像这样的课程:
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 :(得分:1)
我也不想将15个不同的SimpleJdbcCalls注入到我的存储库中,所以我咬紧牙关,并将其添加到我的测试设置方法中:
DatabaseMetaData metaData = mock(DatabaseMetaData.class);
Connection con = mock(Connection.class);
when(con.getMetaData()).thenReturn(metaData);
DataSource ds = mock(DataSource.class);
when(ds.getConnection()).thenReturn(con);
jdbcTemplate = mock(JdbcTemplate.class);
when(jdbcTemplate.getDataSource()).thenReturn(ds);
答案 1 :(得分:0)
我肯定会采用添加构造函数的方法来允许SimpleJdbcCall
直接注入。
MyClass(SimpleJdbcCall simpleJdbcCall) {
this.simpleJdbcCall = simpleJdbcCall;
}
(可能从当前调用new
的那个构造函数中调用该构造函数。)
这不是" hackish",它只是Dependency Injection。我认为让课堂可测试而不需要测试SimpleJdbcCall
的工作方式是一个明显的改进。
在构造函数中调用new
会使测试更加困难,因为它是与实例化的类的紧密静态耦合。
我发现这个主题Miško Hevery's blog post非常有趣。
答案 2 :(得分: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`
}};
}
答案 3 :(得分: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");
}
这个包是私有的,所以同一个包(= tests)中的其他类可以调用它。这样,测试可以创建一个覆盖executeFunction()
的实例。您可以在方法中返回虚假结果或测试对象的状态。
这意味着您的代码仍然配置对象;测试只是传递了一个“POJO”,被测试的代码填满了。
这样,您就不必编写大量代码 - 默认实现可以完成大部分工作。
或者,允许使用接口SimpleJdbcCallOperations
调用构造函数,这意味着您需要一个强大的模拟框架或编写大量的样板代码。
其他替代方法:使用模拟JDBC驱动程序。这些通常很难设置,导致虚假的测试失败,当测试失败时,你通常不知道为什么,......
或内存数据库。它们带来了一大堆问题(您需要加载制造和维护所需的测试数据)。
这就是为什么我尽量避免通过JDBC层进行往返的原因。假设JDBC和数据库正常工作 - 其他人已经测试了这段代码。如果你再次这样做,那你只是在浪费时间。
相关:
答案 4 :(得分:0)
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));
}