Нуждается в рефакторинге для улучшения тестируемости
Я тестирую простой слой DAO с помощью mockito, но обнаружил проблему, в основном сложный для тестирования интерфейс, и мне было интересно, не могли бы вы дать мне некоторое представление...
Это метод, который я хочу проверить:
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 является частью макета, и я тоже заинтересован в тестировании этого кода... я должен сделать отдельный тест для этой конкретной команды?
2 ответа
Вместо использования анонимных классов вы можете создать интерфейс и его реализацию отдельно. Затем метод executeQuery будет иметь строку и этот интерфейс в качестве параметра.
Таким образом, ваш тест останется прежним. Вы сможете разделить метод работы в другом тесте (тест для реализации вашего интерфейса), который выглядит сложным для тестирования.
Результат будет примерно таким:
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);
}
}
Вы можете получить фантазию и "захватить" 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());
}
Я не согласен с тем, нравится ли мне это - это раскрывает многие внутренние аспекты тестируемого вами метода. Но, по крайней мере, это вариант.