我正在使用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 :(得分: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);
}
}
答案 1 :(得分: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());
}
我是否喜欢这个是冲突的 - 它暴露了你正在测试的方法的很多内部结构。但至少它是一种选择。