使用数据填充ResultSet的简便方法

时间:2009-05-18 17:26:18

标签: java unit-testing jdbc mocking resultset

我想模拟一个ResultSet。认真。 我正在重构一个从ResultSet中解析数据的大型复杂代码,我希望我的代码行为相同。因此,我需要为重构的部分编写一个单元测试,以便能够对此进行测试。

谷歌搜索后,我想出了两个想法:

  1. 使用EasyMock,写下looooong模拟序列。非常糟糕的解决方案:很难添加初始数据,难以更改数据,很大的测试调试承诺。
  2. 使用Apache Derby或HSQLDB创建内存数据库,从文件或String数组填充它,查询一些神奇的InMemoryDBUtils.query(sql)。然后使用该ResultSet。不幸的是,我没有找到任何神奇的InMemoryDBUtils快速编写测试:-)。 IBM文章“使用Derby进行持久性的隔离单元测试”似乎对我需要的东西很好,但是......
  3. 第二种方法看起来更容易,也更容易支持。

    你对创建这样的模拟有什么建议? (尽管医生,当然:-)?我错过了眉毛一些银弹吗?可能,DBUnit是这个的工具吗?

6 个答案:

答案 0 :(得分:38)

我从这里获得了MockResultSet类的成功:http://mockrunner.sourceforge.net/。它允许您创建实现ResultSet接口的类,并允许您为每个列和行设置值。

如果您的方法使用合理大小的ResultSet,您应该能够创建能够相当容易地返回所需值的测试。

这是一个简单的例子:

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());

答案 1 :(得分:11)

据我所知,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 :(得分:5)

Mockrunner可以加载CSV或XML文件并自动创建MockResultSet。它还可以模拟Connection和Statement,因此所有JDBC的东西都可以工作,甚至不需要在类路径中添加JDBC驱动程序。

答案 3 :(得分:5)

我为同样的情况写过一些东西。您可以使用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];
    }
}

答案 4 :(得分:2)

如果适用,您可以从实际数据源中获取现在的结果集,序列化它并保存文件。然后你可以为每个单元测试反序列化结果集,你应该很高兴。

答案 5 :(得分:1)

只要您没有调用大多数ResultSet方法,我可能只是将分隔的文本文件加载到二维数组中,并实现我实际需要的方法,其余的则抛出UnsupportedOperationException(这是我IDE中存根方法的默认实现)。

相关问题