如何在java中对jdbc代码进行单元测试?

如何在java中对jdbc代码进行单元测试?,java,database,unit-testing,jdbc,junit,Java,Database,Unit Testing,Jdbc,Junit,我想为一些代码编写一些单元测试,这些代码连接到数据库,运行一个或多个查询,然后处理结果。 (不实际使用数据库) 这里的另一位开发人员编写了我们自己的DataSource、Connection、Statement、PreparedStatement和ResultSet实现,这些实现将基于xml配置文件返回相应的对象。(我们可以使用伪造的数据源,只需对它返回的结果集运行测试) 我们是在重新发明轮子吗?这样的东西已经存在于单元测试中了吗? 还有其他/更好的方法来测试jdbc代码吗?有。它不允许您在没有

我想为一些代码编写一些单元测试,这些代码连接到数据库,运行一个或多个查询,然后处理结果。 (不实际使用数据库)

这里的另一位开发人员编写了我们自己的DataSource、Connection、Statement、PreparedStatement和ResultSet实现,这些实现将基于xml配置文件返回相应的对象。(我们可以使用伪造的数据源,只需对它返回的结果集运行测试)

我们是在重新发明轮子吗?这样的东西已经存在于单元测试中了吗?
还有其他/更好的方法来测试jdbc代码吗?

有。它不允许您在没有数据库的情况下测试jdbc代码,但似乎您可以通过模拟数据库来引入一组不同的代码。

您可以与一个可以从CSV文件中读取初始数据的组件一起使用。

这就是为什么您有(现在称为JavaDB)或者--它们很小,可以相对快速、简单地创建、加载、测试和销毁的简单数据库。

我想说,HSQL是单元测试中的一种方法。测试的重点是测试jdbc代码并确保其正常工作。添加自定义类或模拟jdbc调用可以很容易地隐藏bug


我主要使用mysql,当测试运行时,驱动程序类和url会更改为org.hsqldb.jdbcDriver和jdbc:hsqldb:mem:test。

而在应用程序中模拟jdbc的方式当然取决于您实际实现jdbc事务的方式

如果您按原样使用jdbc,我假设您已经编写了一个实用程序类,用于执行
DBUtils.getMetadataFor(String tablename)
行中的一些任务。这意味着您必须创建该类的模拟,这可能就是您所需要的。这对您来说是一个相当简单的解决方案,因为您显然已经有了一系列与jdbc相关的模拟对象。请注意,我假设您的jdbc代码没有在应用程序中被分解——如果是,重构

但是,如果您正在使用任何用于数据库处理的框架(如Spring framework的JDBC模板类),您可以而且应该使用EasyMock或其他等效工具模拟接口类。这样,你就可以拥有世界上所有的力量来轻松模拟连接


最后,如果其他方法都不起作用,您可以按照其他人已经说过的做,使用DBUnit和/或derby。

您有几个选项:

  • 使用模拟库模拟数据库,例如。这样做的巨大缺点是,您的查询和数据很可能根本不会被测试
  • 使用轻量级数据库进行测试,例如。如果您的查询很简单,那么这可能是最简单的方法
  • 为测试指定一个数据库。是一个很好的选择,或者如果您正在使用Maven,也可以使用来正确地设置和拆除数据库(注意测试之间的依赖关系)。我建议使用此选项,因为它将使您最大程度地相信查询能够与您的db供应商正常工作

有时,使这些测试可配置是必要且有用的,以便仅在数据库可用时执行这些测试。这可以通过构建属性来完成。

我更喜欢使用它来测试不太容易测试的代码。

我们使用Mockrunner。它内置了模拟连接和数据源,因此无需自行实现它们。

我喜欢结合使用:

  • (具体而言,为和)
只要使用DBUnit和HSQLDB,您就可以走得很远。Unitils提供了管理和重置数据库状态的最后一英里代码。它还提供了一种很好的管理数据库模式更改的方法,并使使用特定的RBDM(Oracle、DB2、SQL Server等)变得容易。最后,Unitils围绕DBUnit提供了一些很好的包装,使API现代化,并使DBUnit更易于使用


如果你还没有签出Unitils,你肯定应该签。Unitils经常被忽略和低估。

Acolyte驱动程序可用于模拟JDBC连接,在测试期间对其进行管理,并将数据作为结果集返回(使用其typesafe行列表API):


注意:我是Acolyte的作者。

如果您想进行单元测试,而不是集成测试,那么 您可以使用非常基本和简单的方法,仅使用Mockito,如下所示:

public class JDBCLowLevelTest {

    private TestedClass tested;
    private Connection connection;
    private static Driver driver;

    @BeforeClass
    public static void setUpClass() throws Exception {
        // (Optional) Print DriverManager logs to system out
        DriverManager.setLogWriter(new PrintWriter((System.out)));

        // (Optional) Sometimes you need to get rid of a driver (e.g JDBC-ODBC Bridge)
        Driver configuredDriver = DriverManager.getDriver("jdbc:odbc:url");

        System.out.println("De-registering the configured driver: " + configuredDriver);
        DriverManager.deregisterDriver(configuredDriver);

        // Register the mocked driver
        driver = mock(Driver.class);
        System.out.println("Registering the mock driver: " + driver);
        DriverManager.registerDriver(driver);
    }

    @AfterClass
    public static void tearDown() throws Exception {
        // Let's cleanup the global state
        System.out.println("De-registering the mock driver: " + driver);
        DriverManager.deregisterDriver(driver);
    }

    @Before
    public void setUp() throws Exception {
        // given
        tested = new TestedClass();

        connection = mock(Connection.class);

        given(driver.acceptsURL(anyString())).willReturn(true);
        given(driver.connect(anyString(), Matchers.<Properties>any()))
                .willReturn(connection);

        given(connection.prepareCall(anyString())).willReturn(statement);        
    }
}
看看JDBDT:

您可以将其用于数据库设置和断言,作为DBUnit的替代方案


注意:我是JDBDT的作者。

谢谢,我喜欢使用maven设置/删除数据库的想法。我们现在使用maven进行构建,因此这对我们来说非常容易使用。DBUnit链接()指向一个可能与DBUnit无关的中文网站。@user3885927我修复了该链接。下次您发现这样的问题时,请随时提交编辑以修复断开的链接。但是,任何依赖于供应商特定SQL的代码(轻量级数据库不支持)都将不可测试。@asah:“供应商特定SQL”通常是一个错误。然而,当您测试特定于供应商的SQL时,您并没有进行单元测试,所以这并不是一个真正的单元测试问题,不是吗?在这一点上,它更像是一个集成测试,而不是单元测试。但是在JUnit测试中连接到MySQL与从JUnit测试连接到sqlite并没有本质上的区别。我将这两种场景视为集成测试。真正使其成为单元测试的是在所有数据库接缝处都有模拟或伪造,即根本没有数据库服务器(轻量级或重量级)。话虽如此,我并不热衷于一直坚持这一原则。只要测试运行速度快,不需要大量的手动设置,我就可以。@asph:模拟数据库——虽然技术上可行——通常是愚蠢的。你没有写它,因此你必须相信它。如果你不知道真相
@Test
public void shouldHandleDoubleException() throws Exception {
    // given
    SomeData someData = new SomeData();

    given(connection.prepareCall(anyString()))
            .willThrow(new SQLException("Prepare call"));
    willThrow(new SQLException("Close exception")).given(connection).close();

    // when
    SomeResponse response = testClass.someMethod(someData);

    // then
    assertThat(response, is(SOME_ERROR));
}