Java 用数据填充 ResultSet 的简单方法

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/878848/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 20:34:15  来源:igfitidea点击:

Easy way to fill up ResultSet with data

javaunit-testingjdbcmockingresultset

提问by DiaWorD

I want to mock a ResultSet. Seriously. I'm refactoring one big complicated piece of code which is parsing data from ResultSet, and I want my code to behave identically. So, I need to write a unit test for the piece being refactored to be able to test this.

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

After googling I came up with 2 ideas:

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

  1. Use EasyMock, write looooong mocking sequence. VERY BAD solution: hard to add initial data, hard to change data, big test debugging promices.
  2. Use Apache Derby or HSQLDB to create in-memory DB, fill it from file or String array, query with some magical InMemoryDBUtils.query(sql). Then use that ResultSet. Unfortunately, I did not find any magical InMemoryDBUtils to write the test fast :-). IBM article "Isolated unit testing of persistence with Derby" seems just fine about what I need, though...
  1. 使用 EasyMock,编写 looooong 模拟序列。非常糟糕的解决方案:难以添加初始数据,难以更改数据,大测试调试问题。
  2. 使用 Apache Derby 或 HSQLDB 创建内存数据库,从文件或字符串数​​组填充它,用一些神奇的 InMemoryDBUtils.query(sql) 查询。然后使用该结果集。不幸的是,我没有找到任何神奇的 InMemoryDBUtils 来快速编写测试:-)。IBM 文章“Derby 持久性的隔离单元测试”似乎很好地满足了我的需求,不过……

Second approach looks somewhat easier and much more supportable.

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

What would you advice for creating such a mock? (despite doctors, of course :-)? Am I missing an eyebrowsome silver bullet? Possibly, DBUnit is the tool for this?

你对创建这样一个模拟有什么建议?(当然,尽管有医生:-)?我的眉毛是不是有点银弹?可能,DBUnit 是用于此的工具?

采纳答案by Yishai

DBUnit doesn't present a result set, to my knowledge, although it will well help you populate your in memory database.

据我所知,DBUnit 不提供结果集,尽管它可以很好地帮助您填充内存数据库。

I would say that a mocking framework is the wrong approach at this point. Mocking is about testing behavior and interaction, not just returning data, so it will likely get in your way.

我会说在这一点上模拟框架是错误的方法。模拟是关于测试行为和交互,而不仅仅是返回数据,因此它可能会妨碍您。

I would instead either implement a result set interface, or create a dynamic proxy of a result set interface to a class that implements the methods you care about without having to implement the whole result set. You will likely find maintaining a class as easy as maintaining an in memory database (provided that the dataset under test is consistent), and probably easier to debug.

我要么实现一个结果集接口,要么创建一个结果集接口的动态代理到一个实现你关心的方法的类,而不必实现整个结果集。您可能会发现维护一个类就像维护一个内存数据库一样简单(假设被测数据集是一致的),并且可能更容易调试。

You could back up that class with DBUnit, where you take a snapshot of your result set with dbunit, and have dbunit read it back during the test from xml, and have your dummy result set read the data from dbunit's classes. This would be a reasonable approach if the data was mildly complex.

您可以使用 DBUnit 备份该类,在其中使用 dbunit 拍摄结果集的快照,并让 dbunit 在测试期间从 xml 读取它,并让您的虚拟结果集从 dbunit 的类中读取数据。如果数据稍微复杂,这将是一种合理的方法。

I would go for the in memory database if the classes were so coupled that they need to read data that was modified as part of the same test. Even then, I would consider using a copy of the real database until you managed to pull that dependency apart.

如果这些类是如此耦合以至于它们需要读取作为同一测试的一部分修改的数据,我会选择内存数据库。即便如此,我也会考虑使用真实数据库的副本,直到您设法将这种依赖性分开。

A simple proxy generation method:

一个简单的代理生成方法:

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

回答by GWLlosa

If applicable, you could you take the result set you have now from your real data source, serialize it, and save the file. Then you could deserialize that result set for each of your unit tests, and you should be good to go.

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

回答by erickson

As long as you're not calling most of the ResultSetmethods, I would probably just load a delimited text file into a two-dimensional array, and implement the methods I actually needed, leaving the rest to throw an UnsupportedOperationException(which is the default implementation for stubbed-out methods in my IDE).

只要您不调用大多数ResultSet方法,我可能只是将一个带分隔符的文本文件加载到一个二维数组中,然后实现我实际需要的方法,其余的则抛出一个UnsupportedOperationException(这是在我的 IDE 中删除了方法)。

回答by matt

I've had success with the MockResultSet class from here: http://mockrunner.sourceforge.net/. It allows you to create a class that implements the ResultSet interface, and lets you set the values for each column and row.

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

If your methods are working with ResultSets of reasonable size, you should be able to create tests that return the values you need fairly easily.

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

Here's a simple example:

这是一个简单的例子:

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

回答by jason

Mockrunnercan load a CSV or XML file and create a MockResultSet automatically. It can also mock Connection and Statement, so all your JDBC stuff simply works, without even adding a JDBC driver to your classpath.

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

回答by karthik m

I have written something for this same case. You can mock the resultset using Mockito. You can as well loop over the mock rows of resultset by mocking the resultset.next() with this piece of code.

我为同一个案例写了一些东西。您可以使用 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];
    }
}