Простой способ заполнить ResultSet данными - PullRequest
21 голосов
/ 18 мая 2009

Я хочу издеваться над ResultSet. Шутки в сторону. Я выполняю рефакторинг одного большого сложного фрагмента кода, который анализирует данные из ResultSet, и я хочу, чтобы мой код вел себя одинаково. Итак, мне нужно написать модульный тест для части, которая подвергается рефакторингу, чтобы иметь возможность проверить это.

После поиска в Google я пришел к двум идеям:

  1. Используйте EasyMock, напишите Looooong насмешливую последовательность. ОЧЕНЬ ПЛОХОЕ решение: сложно добавить исходные данные, сложно изменить данные, большие отладочные тесты.
  2. Используйте Apache Derby или HSQLDB, чтобы создать БД в памяти, заполнить ее из файла или массива String, выполнить запрос с каким-нибудь волшебным InMemoryDBUtils.query (sql). Затем используйте этот ResultSet. К сожалению, я не нашел никаких волшебных InMemoryDBUtils, чтобы быстро написать тест :-). Статья IBM "Изолированное модульное тестирование персистентности с помощью Derby", похоже, прекрасно подходит мне, хотя ...

Второй подход выглядит несколько проще и гораздо более приемлемым.

Что бы вы посоветовали для создания такого макета? (несмотря на врачей, конечно :-)? Мне не хватает брови какой-то серебряной пули? Возможно, DBUnit является инструментом для этого?

Ответы [ 6 ]

38 голосов
/ 18 мая 2009

У меня был успех с классом 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());
11 голосов
/ 18 мая 2009
Насколько мне известно,

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));
}
5 голосов
/ 25 января 2013

Я написал что-то для этого же случая. Вы можете смоделировать набор результатов, используя Mockito. Вы также можете перебрать фиктивные строки набора результатов, смоделировав resultset.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 голосов
/ 21 января 2010

Mockrunner может загрузить файл CSV или XML и автоматически создать MockResultSet. Он также может имитировать Connection и Statement, поэтому все ваши JDBC-вещи просто работают, даже не добавляя драйвер JDBC в ваш путь к классам.

2 голосов
/ 18 мая 2009

Если применимо, вы можете взять имеющийся у вас набор результатов из вашего реального источника данных, сериализовать его и сохранить файл. Затем вы можете десериализовать этот набор результатов для каждого из ваших модульных тестов, и вы должны быть готовы.

1 голос
/ 18 мая 2009

Пока вы не вызываете большинство методов ResultSet, я бы, вероятно, просто загрузил текстовый файл с разделителями в двумерный массив и реализовал методы, которые мне действительно нужны, оставив остальные для выброса UnsupportedOperationException (это реализация по умолчанию для заглушенных методов в моей IDE).

...