【问题标题】:Easy way to fill up ResultSet with data用数据填充 ResultSet 的简单方法
【发布时间】:2010-10-27 02:28:53
【问题描述】:

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

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

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

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

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

【问题讨论】:

    标签: java unit-testing jdbc mocking resultset


    【解决方案1】:

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

    【讨论】:

    • 您好 mjd79,感谢您的回答。我的问题是每个测试用例肯定有大约 100 条记录和大约 10 个测试用例 :-) 每条记录有 10 个字段,包括日期、数字和字符串。因此,我更喜欢将模拟数据存储在文件中,最好以 INSERT INTO 形式存储,它可以直接从几乎每个数据库客户端获取。 MockRunner 看起来很不错,但我会保留它以备不时之需。再次感谢您的宝贵时间。
    • mockrunner 是模拟批处理作业测试的简单结果集的绝佳资源。非常感谢!
    【解决方案2】:

    据我所知,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));
    }
    

    【讨论】:

    • 您好,一晒,感谢您的反馈。我将为 10 个或更多测试中的每一个模拟大约 100 条记录,记录由 10 个字段组成。这,AFAIU,让我使用 DBUnit 将数据存储在文件和自定义 ResultSet 实现中。再次感谢您的回答。
    • 不客气。我想说,如果 10 个测试中的每一个都需要不同的数据,那么是的,这将是一种合理的方法。使用 DBUnit,您可以获取一个 ResultSet 并将其写入 XML,因此您只需在测试中引用它。
    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

      我为同样的情况写了一些东西。您可以使用 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】:

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

        【讨论】:

        • 您好 GWLlosa,也感谢您的反馈。我认为 DBUnit 将帮助我将数据存储在文件中。
        【解决方案6】:

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

        【讨论】:

          猜你喜欢
          • 2012-05-24
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-05-28
          • 1970-01-01
          • 1970-01-01
          • 2011-01-11
          • 2012-08-12
          相关资源
          最近更新 更多