2009-07-17 3 views
1

Я тестирую простой слой 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: если вам нужна дополнительная информация, не стесняйтесь спросить


EDIT: тест, что трудно сделать

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

Просьба привести пример теста, который усложняется. –

+0

Я не знаю Java хорошо, но я бы сказал, что ResultSetCommand не имеет значения. Вы тестируете getPersonById, чтобы убедиться, что он возвращает правильного Person, когда ему задан действительный UserId, выдает исключение, когда ему присваивается недопустимое значение, и т. Д. Если он работает правильно, вам все равно, что он использует конкретный ResultSetCommand. –

ответ

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

Итак, вы говорите, что я делаю 2 теста, один для PersonDAO и другой для анонимного класса ResultSetCommand (не более)? –

+0

@Pablo Да, тесты были бы раздельными (учитывая, что вы хотите его протестировать, а не проводить интеграционный тест).Если вы хотите «более крупный тест изображения», вы можете сделать свой executeQuery возвратом только ResultSet и использовать его в PersonDAO. Посмотрите, что для вас наиболее важно –

+0

Я впервые попробовал этот подход (возвращая RS), но он бросил странные ошибки, я считаю, потому что RS не мог работать с закрытым соединением или что-то в этом роде, и я не хотел давать вызывающая сторона несет ответственность за закрытие RS или соединения –

0

Вы можете получить фантазии и "захватить" ResultSetCommand аргумент, а затем имитировать обратный вызов по нему макет 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()); 
} 

Я не согласен с тем, нравится ли мне это - i t предоставляет много внутренних компонентов метода, который вы тестируете. Но, по крайней мере, это вариант.

Смежные вопросы