Нуждается в рефакторинге для улучшения тестируемости

Я тестирую простой слой 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());
}

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

Другие вопросы по тегам