Простой способ заполнить ResultSet данными
Я хочу высмеять ResultSet. Шутки в сторону.
Я реорганизую один большой сложный фрагмент кода, который анализирует данные из ResultSet, и я хочу, чтобы мой код вел себя одинаково. Поэтому мне нужно написать unit test для рефакторинга, чтобы проверить это.
После googling я придумал 2 идеи:
- Используйте EasyMock, напишите looooong mocking sequence. Решение VERY BAD: трудно добавлять исходные данные, трудно изменить данные, большие отладочные обещания для тестирования.
- Используйте Apache Derby или HSQLDB для создания БД в памяти, заполните его из файла или массива String, запросите некоторые магические InMemoryDBUtils.query(sql). Затем используйте этот ResultSet. К сожалению, я не нашел волшебных InMemoryDBUtils, чтобы быстро написать тест:-). Статья IBM "Изолированное единичное тестирование настойчивости с Derby" кажется просто прекрасным в том, что мне нужно, хотя...
Второй подход выглядит несколько проще и гораздо более удобным.
Что бы вы посоветовали создать такой макет? (несмотря на врачей, конечно:-)? Я пропустил <ударить > бровь серебряную пулю? Возможно, DBUnit - это инструмент для этого?
Ответы
Ответ 1
DBUnit, насколько мне известно, не представляет набор результатов, хотя он хорошо поможет вам заполнить вашу базу данных в памяти.
Я бы сказал, что насмешливая структура - это неправильный подход на данный момент. Стыковка - это тестирование поведения и взаимодействия, а не просто возврат данных, поэтому он, скорее всего, будет мешать вам.
Вместо этого я либо реализую интерфейс набора результатов, либо создаю динамический прокси-интерфейс интерфейса результирующего набора для класса, который реализует методы, о которых вы заботитесь, без необходимости реализации всего набора результатов. Скорее всего, вы сохраните класс таким же простым, как поддержание базы данных в памяти (при условии, что тестируемый набор данных согласован) и, вероятно, легче отлаживать.
Вы можете создать резервную копию этого класса с помощью DBUnit, где вы делаете снимок вашего результирующего набора с помощью dbunit и dbunit читаете его во время теста из xml, и ваш фиктивный набор результатов считывает данные из классов dbunit. Это было бы разумным подходом, если бы данные были слегка сложными.
Я бы пошел в базу данных в памяти, если классы были настолько связаны, что им нужно было прочитать данные, которые были изменены как часть одного и того же теста. Даже тогда я хотел бы использовать копию реальной базы данных, пока вам не удастся отделить эту зависимость.
Простой способ генерации прокси:
private static class SimpleInvocationHandler implements InvocationHandler {
private Object invokee;
public SimpleInvocationHandler(Object invokee) {
this.invokee = invokee;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
method = invokee.getClass().getMethod(method.getName(), method.getParameterTypes());
if (!method.isAccessible()) {
method.setAccessible(true);
}
try {
return method.invoke(invokee, args);
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
}
}
public static <T> T generateProxy(Object realObject, Class... interfaces) {
return (T) Proxy.newProxyInstance(realObject.getClass().getClassLoader(), interfaces, new SimpleInvocationHandler(realObject));
}
Ответ 2
У меня был успех с классом MockResultSet отсюда: http://mockrunner.sourceforge.net/. Он позволяет создать класс, который реализует интерфейс ResultSet, и позволяет устанавливать значения для каждого столбца и строки.
Если ваши методы работают с ResultSets разумного размера, вы должны иметь возможность создавать тесты, которые возвращают нужные вам значения довольно легко.
Вот простой пример:
MockResultSet rs = new MockResultSet("myMock");
rs.addColumn("columnA", new Integer[]{1});
rs.addColumn("columnB", new String[]{"Column B Value"});
rs.addColumn("columnC", new Double[]{2});
// make sure to move the cursor to the first row
try
{
rs.next();
}
catch (SQLException sqle)
{
fail("unable to move resultSet");
}
// process the result set
MyObject obj = processor.processResultSet(rs);
// run your tests using the ResultSet like you normally would
assertEquals(1, obj.getColumnAValue());
assertEquals("Column B Value", obj.getColumnBValue());
assertEquals(2.0d, obj.getColumnCValue());
Ответ 3
Mockrunner может загружать CSV или XML файл и автоматически создавать MockResultSet. Он также может высмеять Connection и Statement, поэтому все ваши материалы JDBC просто работают, даже не добавляя JDBC-драйвер в ваш путь к классам.
Ответ 4
Я написал что-то для этого же случая. Вы можете издеваться над набором результатов с помощью Mockito. Вы можете также петли над макетными строками набора результатов, высмеивая resultet.next() с помощью этого фрагмента кода.
// two dimensional array mocking the rows of database.
String[][] result = { { "column1", "column2" }, { "column1", "column2" } };
@InjectMocks
@Spy
private TestableClass testableClass;
@Mock
private Connection connection;
@Mock
private Statement statement;
@Mock
private ResultSet resultSet;
@BeforeTest
public void beforeTest() {
MockitoAnnotations.initMocks(this);
}
@BeforeMethod
public void beforeMethod() throws SQLException {
doAnswer(new Answer<Connection>() {
public Connection answer(InvocationOnMock invocation)
throws Throwable {
return connection;
}
}).when(testableClass).getConnection();
when(connection.createStatement()).thenReturn(statement);
when(statement.executeQuery(anyString())).thenReturn(resultSet);
final AtomicInteger idx = new AtomicInteger(0);
final MockRow row = new MockRow();
doAnswer(new Answer<Boolean>() {
@Override
public Boolean answer(InvocationOnMock invocation) throws Throwable {
int index = idx.getAndIncrement();
if (result.length > index) {
String[] current = result[index];
row.setCurrentRowData(current);
return true;
} else
return false;
}
;
}).when(resultSet).next();
doAnswer(new Answer<String>() {
@Override
public String answer(InvocationOnMock invocation) throws Throwable {
Object[] args = invocation.getArguments();
int idx = ((Integer) args[0]).intValue();
return row.getColumn(idx);
}
;
}).when(resultSet).getString(anyInt());
}
static class MockRow {
String[] rowData;
public void setCurrentRowData(String[] rowData) {
this.rowData = rowData;
}
public String getColumn(int idx) {
return rowData[idx - 1];
}
}
Ответ 5
Если применимо, вы можете взять набор результатов, который у вас есть сейчас, из вашего реального источника данных, сериализовать его и сохранить файл. Затем вы можете десериализовать этот результирующий набор для каждого из ваших модульных тестов, и вам должно быть хорошо идти.
Ответ 6
Пока вы не вызываете большинство методов ResultSet
, я бы, вероятно, просто загрузил текстовый файл с разделителями в двумерный массив и реализовал нужные мне методы, оставив остальным бросить UnsupportedOperationException
(который является реализацией по умолчанию для методов stubbed-out в моей среде IDE).