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

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

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

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

第二种方法看起来更容易,也更容易支持。

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

据我所知,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 generateProxy(Object realObject, Class... interfaces) { return (T) Proxy.newProxyInstance(realObject.getClass().getClassLoader(), interfaces, new SimpleInvocationHandler(realObject)); } 

我从这里获得了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()); 

我为同样的案例写过一些东西。 您可以使用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() { 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() { @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() { @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]; } } 

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

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

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