如何模拟要测试的数据库(Java)?


76

我正在用Java编程,而我的应用程序正在大量使用DB。因此,对我来说重要的是能够轻松测试数据库的使用情况。
什么是DB测试?对我来说,他们应该提供两个简单的要求:

  1. 验证SQL语法。
  2. 更重要的是,根据给定情况检查数据是否正确选择/更新/插入。

那么,看来我只需要一个DB。
但是实际上,我不喜欢,因为使用数据库进行测试几乎没有困难:

  • “只给自己一个测试数据库,这有多难?” -嗯,在我的工作场所,拥有个人测试数据库几乎是不可能的。您必须使用每个人都可以访问的“公共”数据库。
  • “这些测试肯定不会很快...”-DB测试往往比常规测试慢。进行缓慢的测试真的不理想。
  • “此程序应处理任何情况!” -在数据库中尝试模拟每种情况变得有些烦人,甚至是不可能的。对于每种情况,都应进行一定数量的插入/更新查询,这很烦人并且需要时间。
  • “请稍等,您怎么知道该表中有542行?” -测试的主要原则之一是能够以与测试代码不同的方式测试功能。使用数据库时,通常有一种方法可以执行某项操作,因此测试与核心代码完全相同。

因此,您可以发现测试时我不喜欢数据库(当然,在某些时候,我必须要解决这个问题,但是当我发现大多数错误是通过使用其余的测试方法)。但是我在找什么呢?

我正在寻找一种使用文件系统或虚拟内存来模拟数据库,模拟数据库的方法。我以为可能有一个Java工具/程序包,可让每个测试简单地(使用代码接口)构造一个数据库模拟,模拟表和行,SQL验证以及用于监视其状态的代码接口(而不是使用SQL) )。

您熟悉这种工具吗?


编辑:感谢您的答案!尽管我在寻找工具,但是您也向我提供了有关该问题的提示:)我将花一些时间检查您的报价,所以我现在无法确定您的答案是否令人满意。

无论如何,这是我正在寻找的更好的视图-想象一下一个名为DBMonitor的类,它的功能之一就是查找表中的行数。这是我想如何使用JUnit测试该功能的虚构代码:

public class TestDBMonitor extends TestCase {

    @Override
    public void setUp() throws Exception {

       MockConnection connection = new MockConnection();

       this.tableName = "table1";
       MockTable table = new MockTable(tableName);

       String columnName = "column1";
       ColumnType columnType = ColumnType.NUMBER;
       int columnSize = 50;
       MockColumn column = new MockColumn(columnName, columnType, columnSize);
       table.addColumn(column);

       for (int i = 0; i < 20; i++) {
           HashMap<MockColumn, Object> fields = new HashMap<MockColumn, Object>();
           fields.put(column, i);
           table.addRow(fields);
       }

       this.connection = connection;
    }

    @Test
    public void testGatherStatistics() throws Exception {

       DBMonitor monitor = new DBMonitor(connection);
       monitor.gatherStatistics();
       assertEquals(((MockConnection) connection).getNumberOfRows(tableName),
                    monitor.getNumberOfRows(tableName));
    }

    String tableName;
    Connection connection;
}

我希望这段代码足够清晰,足以理解我的想法(对我来说,语法错误,我没有亲爱的Eclipse:P时手动输入)。

顺便说一下,我部分使用ORM,而我的原始SQL查询非常简单,在一个平台与另一个平台之间应该没有什么不同。

Answers:


23

旧问题的新答案(但情况有所发展):

如何模拟要测试的数据库(Java)?

您不会模拟它。您嘲笑您的存储库,但不对其进行测试,或者在测试中使用相同的数据库,然后对sql进行测试。所有内存数据库都不完全兼容,因此它们不会为您提供全面的覆盖范围和可靠性。永远不要尝试模拟/模拟连接,结果集等深层db对象。它根本没有任何价值,是开发和维护的噩梦

拥有个人测试数据库几乎是不可能的。您必须使用一个“公共”数据库,每个人都可以使用

不幸的是,许多公司仍然使用该模型,但是现在我们有了docker,几乎每个数据库都有映像。商业产品有一些局限性(例如,高达几GB的数据)对于测试而言并不重要。您还需要在此本地数据库上创建架构和结构

“这些测试肯定不会很快...”-DB测试往往比常规测试慢。进行缓慢的测试真的不理想。

是的,数据库测试速度较慢,但​​并不慢。我做了一些简单的测量,一个典型的测试花费了5-50ms。需要时间的是应用程序启动。有很多方法可以加快速度:

  • 第一个DI框架(例如spring)提供了仅运行应用程序某些部分的方式。如果您编写的应用程序将db和与db无关的逻辑很好地分开,那么在您的测试中,您只能启动db部分
  • 每个数据库都有大量的调优选项,使其耐用性降低,速度更快。非常适合测试。postgres的例子
  • 您也可以将整个数据库放入tmpfs

  • 另一个有用的策略是使测试组保持默认状态,并关闭数据库测试(如果它们确实减慢了构建速度)。这样,如果某人实际上正在处理db,则需要在cmd行中传递其他标志或使用IDE(testng组和自定义测试选择器非常适合此操作)

对于每种情况,都应进行一定数量的插入/更新查询,这很烦人并且需要时间

上面讨论了“花时间”部分。讨厌吗 我看过两种方法:

  • 为所有测试用例准备一个数据集。那么您必须维护它并对此进行推理。通常,它与代码分开。它具有千字节或兆字节。在一个屏幕上看到,理解和推理是很大的。它引入了测试之间的耦合。因为当您需要更多行用于测试A时,您count(*)的测试B会失败。它之所以增长,是因为即使删除某些测试,您也不知道该测试仅使用了哪些行
  • 每个测试准备其数据。这样,每个测试都是完全独立,可读且易于推理的。讨厌吗 imo,一点也不!它使您可以非常快速地编写新测试,并节省了以后的工作量

您怎么知道该表中有542行?”-测试的主要原则之一就是能够以与测试代码不同的方式测试功能

嗯...不是真的。主要原理是检查您的软件是否响应特定输入生成了所需的输出。因此,如果您拨打dao.insert542次,然后dao.count返回542,则表示您的软件按指定方式工作。如果需要,可以在两者之间调用提交/删除缓存。当然,有时您想测试实现而不是合同,然后检查dao是否更改了数据库的状态。但您始终使用sql B测试sql A(插入vs选择,序列next_val vs返回值等)。是的,您始终会遇到“谁将测试我的测试”的问题,答案是:没有人,因此请保持简单!

其他可能帮助您的工具:

  1. 测试容器将帮助您提供真实的数据库。

  2. dbunit-将帮助您清除测试之间的数据

    缺点:

    • 创建和维护架构和数据需要大量工作。特别是在您的项目处于密集开发阶段时。
    • 这是另一个抽象层,因此,如果您突然想使用此工具不支持的某些数据库功能,可能很难对其进行测试
  3. testegration-旨在为您提供完整的,随时可用的和可扩展的生命周期(公开:我是创作者)。

    缺点:

    • 仅针对小型项目免费
    • 非常年轻的项目
  4. 飞路liquibase -数据库迁移工具。它们可以帮助您轻松地在本地db上创建架构和所有结构以进行测试。


6
我得把它交给你,我不认为有人会再问这个问题而烦恼写一个更新的答案。我8年前问过这个问题,从那时起,我积累了经验,使我大体上同意您的回答-尤其是关于“使用相同代码测试功能”的部分。
艾尔·罗斯

39

Java随附Java DB

也就是说,除非您经过ORM层,否则我建议不要使用与生产中使用的DB不同的类型。否则,您的SQL可能不会像您想象的那样跨平台。

还要签出DbUnit


10

为此,我使用了Hypersonic。基本上,它是一个JAR文件(纯Java内存数据库),您可以在其自己的JVM中运行,也可以在自己的JVM中运行,并且在运行时,您拥有一个数据库。然后停止它,数据库就消失了。到目前为止,我已经将其用作纯内存数据库。在运行单元测试时,通过Ant启动和停止非常简单。


10

关于如何测试集成点,例如通过SQL的数据库连接,存在很多观点。适用于我的个人规则如下:

1)将数据库访问逻辑和功能与常规业务逻辑分开,并将其隐藏在接口后面。原因:为了测试系统中的绝大多数逻辑,最好使用虚拟机/存根代替实际的数据库,因为它更简单。原因2:速度更快

2)将数据库的测试视为与单元测试的主体分开的集成测试,并且需要在设置数据库上运行原因:测试的速度和质量

3)每个开发人员都需要他们自己的独立数据库。他们将需要一种自动化的方法来根据队友的变化来更新其结构并引入数据。参见第4点和第5点。

4)使用http://www.liquibase.org之类的工具来管理数据库结构中的升级。原因:使您能够灵活地更改现有结构并向前发展版本

5)使用http://dbunit.sourceforge.net/之类的工具来管理数据。为特定的测试用例和基础数据设置方案文件(xml或XLS),并且仅清除任何一个测试用例所需的内容。原因:比手动插入和删除数据要好得多原因2:测试人员更容易理解如何调整方案原因3:执行此操作更快

6)您需要功能测试,这些测试也具有类似场景数据的DBUnit,但这是更大的数据集,并执行整个系统。这样就完成了将以下知识相结合的步骤:a)运行单元测试,因此逻辑是正确的。b)对数据库运行和SQL的集成测试是正确的,从而导致“并且整个系统作为一个整体一起工作,底部堆栈”

到目前为止,这种组合为我提供了很好的服务,可以实现高质量的测试和产品,并保持单元测试开发的速度和变更的敏捷性。


5

“只给自己一个测试数据库,这有多难?” -嗯,在我的工作场所,拥有个人测试数据库几乎是不可能的。您必须使用每个人都可以访问的“公共”数据库。

听起来您在工作中遇到了文化问题,这为您无法最大程度地发挥自己的能力和产品优势带来了障碍。您可能想对此做些事情。

另一方面,如果数据库模式受版本控制,那么您始终可以拥有一个测试构建,该测试构建可以从该模式创建数据库,并用测试数据填充数据库,运行测试,收集结果,然后删除数据库。它仅在测试期间存在。如果硬件有问题,它可以是现有安装中的新数据库。这类似于我们在工作场所所做的事情。


5

如果您在工作中使用Oracle,则可以使用“闪回数据库中的还原点”功能使数据库返回到测试之前的某个时间。这将清除您个人对数据库所做的任何更改。

看到:

https://docs.oracle.com/cd/E11882_01/backup.112/e10642/flashdb.htm#BRADV71000

如果您需要用于Oracle生产/工作的测试数据库,则从Oracle查找XE Express Edition数据库。它是免费供个人使用,数据库的大小限制为小于2GB。


3

我们最近切换到JavaDB或Derby来实现这一点。Derby 10.5.1.1现在实现了内存中表示,因此它运行非常快,不需要转到磁盘: Derby In Memory Primer

我们将应用程序设计为可在Oracle,PostgreSQL和Derby上运行,因此在发现一个数据库支持其他数据库不支持的功能之前,不要在任何一个平台上走得太远。


1

我同意班卓琵琶。设置隔离的开发和测试环境应该是当务之急。我使用的每个数据库系统都是开源的,或者具有免费的开发人员版本,您可以将其安装在本地工作站上。这使您可以使用与生产时相同的数据库方言进行开发,使您可以对开发数据库进行完全管理员访问,并且比使用远程服务器要快。


1

尝试使用derby。它既简单又便携式。使用Hibernate,您的应用程序变得更加灵活。在德比上进行测试,在您喜欢和信任的任何事物上进行生产。


1

我们现在正在创建一个数据库测试环境。我们认为我们必须使用具有模拟数据真实数据库管理系统。模拟DBMS的一个问题是SQL从来没有真正完全胶合为标准,因此,人工测试环境必须忠实地支持我们生产数据库的方言。另一个问题是,我们大量使用了列值约束,外键约束和唯一约束,并且由于人工工具可能无法实现这些约束,因此我们的单元测试可以通过,但是当系统测试首次达到实际水平时,系统测试将失败约束。如果测试花费的时间太长,则表明实现错误,我们将优化查询(与生产相比,测试数据集通常很小)。

我们已经在每台开发人员机器以及持续集成和测试服务器(我们使用Hudson)上安装了一个真正的DBMS。我不知道您的工作政策限制是什么,但是安装和使用PostgreSQL,MySQL和Oracle XE相当容易。这些都是免费供开发使用的(甚至是Oracle XE),因此没有合理的理由禁止使用它们。

关键问题是如何确保测试始终在数据库处于一致状态时开始?如果所有测试均为只读,则没有问题。如果您可以设计变异测试以使其始终在永不提交的事务中运行,那就没问题了。但是通常您需要担心会撤消更新。为此,您可以将初始状态导出到文件中,然后将其导入测试后(Oracle的exp和imp shell命令执行此操作)。或者,您可以使用检查点/回滚。但是,更优雅的方法是使用dbunit之类的工具,它对我们来说效果很好。

这样做的主要优点是,我们可以在前面捕获许多错误,这些错误更容易修复,并且当开发人员狂热地尝试调试问题时,我们的真实系统测试也不会受到阻碍。这意味着我们可以更快,更轻松地生成更好的代码。



1

jOOQ是一种工具,除了提供SQL抽象之外,还内置了一些小型工具,例如SPI,可以模拟整个JDBC。如本博客文章所述,这可以通过两种方式进行

通过实施MockDataProviderSPI:

// context contains the SQL string and bind variables, etc.
MockDataProvider provider = context -> {

    // This defines the update counts, result sets, etc.
    // depending on the context above.
    return new MockResult[] { ... }
};

在上述实现中,您可以以编程方式截取每个SQL语句并为其返回结果,甚至可以通过“解析” SQL字符串以提取某些谓词/表信息等来动态地对其进行返回。

通过使用更简单(但功能不那么强大) MockFileDatabase

...,其格式类似于以下内容(一组语句/结果对):

select first_name, last_name from actor;
> first_name last_name
> ---------- ---------
> GINA       DEGENERES
> WALTER     TORN     
> MARY       KEITEL   
@ rows: 3

然后可以按以下方式读取和使用以上文件:

import static java.lang.System.out;
import java.sql.*;
import org.jooq.tools.jdbc.*;

public class Mocking {
    public static void main(String[] args) throws Exception {
        MockDataProvider db = new MockFileDatabase(
            Mocking.class.getResourceAsStream("/mocking.txt");

        try (Connection c = new MockConnection(db));
            Statement s = c.createStatement()) {

            out.println("Actors:");
            out.println("-------");
            try (ResultSet rs = s.executeQuery(
                "select first_name, last_name from actor")) {
                while (rs.next())
                    out.println(rs.getString(1) 
                        + " " + rs.getString(2));
            }
        }
    }
}

请注意,我们如何直接使用JDBC API,而不实际连接到任何数据库。

请注意,我为jOOQ的供应商工作,所以这个答案是有偏见的。

当心,在某个时候,您正在实现整个数据库

以上适用于简单情况。但是请注意,最终您将实现整个数据库。你要:

  1. 验证SQL语法。

好了,通过如上所述模拟数据库,您可以“验证”语法,因为任何上述模拟方法都将拒绝您未在上述确切版本中预见到的每种语法。

您可以实现一个解析器来解析SQL(或者再次使用jOOQ),然后将SQL语句转换为更易于识别并为其生成结果的内容。但是最终,这仅意味着实现整个数据库。

  1. 更重要的是,根据给定情况检查数据是否正确选择/更新/插入。

这使事情变得更加艰难。如果先运行插入然后更新,则结果显然与先更新然后插入不同,因为更新可能会或可能不会影响插入的行。

您如何确保在“模拟”数据库时发生这种情况?您需要一个状态机来记住每个“模拟”表的状态。换句话说,您将实现一个数据库。

嘲笑只会带你走这么远

正如piotrek所说,嘲笑只会带您走这么远。在仅需要拦截几个非常知名的查询的简单情况下,它很有用。如果要模拟整个系统的数据库,这是不可能的。在这种情况下,请使用实际的数据库,最好是与生产中使用的产品相同。


1

我认为我的Acolyte框架可用于此类数据库模拟:https : //github.com/cchantep/acolyte

它允许通过您负责查询/更新处理的连接来运行现有的Java(用于测试):返回适当的结果集,更新计数或根据执行情况进行警告。


0

首先,您是否使用任何ORM层进行数据库访问?
如果不是:那么您的想法将毫无用处。当您不确定要触发的SQL是否可以与生产环境中的DB一起使用时(例如在使用其他情况下的测试用例中),测试的用途是什么。
如果是:那么您可以查看所指出的各种选项。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.