24

我想模拟一个结果集。严重地。我正在重构一大段复杂的代码,它正在解析来自 ResultSet 的数据,并且我希望我的代码具有相同的行为。因此,我需要为正在重构的部分编写一个单元测试,以便能够对此进行测试。

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

  1. 使用 EasyMock,编写 looooong 模拟序列。非常糟糕的解决方案:难以添加初始数据,难以更改数据,大型测试调试promice。
  2. 使用 Apache Derby 或 HSQLDB 创建内存数据库,从文件或字符串数​​组中填充它,使用一些神奇的 InMemoryDBUtils.query(sql) 进行查询。然后使用该结果集。不幸的是,我没有找到任何神奇的 InMemoryDBUtils 来快速编写测试:-)。IBM 文章“使用 Derby 进行持久性的隔离单元测试”似乎很好地满足了我的需求,不过......

第二种方法看起来更容易并且更受支持。

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

4

6 回答 6

39

我从这里的 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());
于 2009-05-18T18:07:34.840 回答
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));
}
于 2009-05-18T17:52:36.837 回答
7

Mockrunner可以加载 CSV 或 XML 文件并自动创建 MockResultSet。它还可以模拟 Connection 和 Statement,因此您的所有 JDBC 内容都可以正常工作,甚至无需将 JDBC 驱动程序添加到您的类路径中。

于 2010-01-21T00:59:21.373 回答
6

我为同样的情况写了一些东西。您可以使用 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];
    }
}
于 2013-01-25T00:06:58.813 回答
2

如果适用,您可以从真实数据源中获取您现在拥有的结果集,对其进行序列化并保存文件。然后,您可以为每个单元测试反序列化该结果集,您应该一切顺利。

于 2009-05-18T17:30:14.823 回答
1

只要你不调用大多数ResultSet方法,我可能只是将一个分隔的文本文件加载到一个二维数组中,并实现我实际需要的方法,剩下的抛出一个UnsupportedOperationException(这是默认实现我的 IDE 中的存根方法)。

于 2009-05-18T17:32:17.080 回答