【问题标题】:How to simulate a DB for testing (Java)?如何模拟用于测试的数据库(Java)?
【发布时间】:2010-10-30 01:11:24
【问题描述】:

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

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

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

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

所以,你可以发现我在测试方面不喜欢 DB(当然,我必须在某些时候解决这个问题,但我宁愿稍后在测试时到达那里,在我发现大多数使用其余测试方法的错误)。但我在寻找什么?

我正在寻找一种方法来模拟数据库、模拟数据库、使用文件系统或仅使用虚拟内存。我认为也许有一个 Java 工具/包允许简单地构造(使用代码接口)每个测试的 DB 模拟,模拟表和行,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 查询非常简单,应该不会因平台而异。

【问题讨论】:

    标签: java database unit-testing testing jdbc


    【解决方案1】:

    Java 自带Java DB

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

    也可以查看DbUnit

    【讨论】:

      【解决方案2】:

      旧问题的新答案(但事情已经向前发展了一点):

      如何模拟数据库进行测试(Java)?

      你没有模拟它。您模拟您的存储库并且您不测试它们,或者您在测试中使用相同的数据库并测试您的 sqls。所有内存数据库都不完全兼容,因此它们不会为您提供完整的覆盖范围和可靠性。并且永远不要尝试模拟/模拟连接、结果集等深层数据库对象。它根本没有给你任何价值,而且是开发和维护的噩梦

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

      不幸的是,很多公司仍在使用该模型,但现在我们有了docker,并且几乎每个数据库都有图像。商业产品有一些对测试不重要的限制(比如最多几 GB 的数据)。您还需要在此本地数据库上创建架构和结构

      “这些测试肯定不快...” - DB 测试往往比通常的测试慢。慢速测试确实不太理想。

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

      • 第一个 DI 框架(如 spring)提供了一种只运行应用程序的某些部分的方法。如果您编写的应用程序很好地分离了数据库和非数据库相关逻辑,那么在您的测试中您可以start only the db part
      • 每个 db 都有大量的调整选项,使其不那么耐用且速度更快。这非常适合测试。 postgres example
      • 你也可以把整个db放到tmpfs中

      • 另一个有用的策略是设置测试组并默认关闭数据库测试(如果它们确实会减慢您的构建速度)。这样,如果有人实际上在使用 db,他需要在 cmd 行中传递额外的标志或使用 IDE(testng 组和自定义测试选择器非常适合此)

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

      “需要时间”部分已在上面讨论过。烦人吗?我见过两种方法:

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

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

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

      其他可能对您有所帮助的工具:

      1. testcontainers 将帮助您提供 真正的数据库。

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

        缺点:

        • 创建和维护模式和数据需要做很多工作。尤其是当您的项目处于密集开发阶段时。
        • 它是另一个抽象层,所以如果您突然想使用该工具不支持的某些 db 功能,可能很难对其进行测试
      3. testegration - 旨在为您提供完整、随时可用和可扩展的生命周期(披露:我是一个创造者)。

        缺点:

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

      【讨论】:

      • 我必须把它交给你,我不认为有人会重新审视这个问题并费心写一个更新的答案。 8 年前我问过这个问题,从那以后我获得了经验,这使我基本同意你的回答——尤其是关于“使用相同代码测试功能”的部分。
      【解决方案3】:

      关于如何通过 SQL 测试数据库连接等集成点有很多观点。我个人的一套对我很有效的规则如下:

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

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

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

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

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

      6) 您需要功能测试,其中还包含类似 DBUnit 的场景数据,但这是更大的数据集并执行整个系统。这样就完成了结合知识的步骤 a)单元测试运行,因此逻辑是合理的 b) 对数据库的集成测试运行且 SQL 正确 导致“整个系统作为一个从上到下的堆栈一起工作”

      到目前为止,这种组合在实现高质量的测试和产品以及保持单元测试开发的速度和更改的敏捷性方面为我提供了很好的帮助。

      【讨论】:

        【解决方案4】:

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

        【讨论】:

          【解决方案5】:

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

          听起来您在工作中遇到了文化问题,这些问题阻碍了您充分发挥自己的能力和产品的优势。你可能想对此做点什么。

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

          【讨论】:

            【解决方案6】:

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

            见:

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

            如果您需要用于 Oracle 生产/工作的测试数据库,请查找 Oracle 的 XE、快递版数据库。这是免费供个人使用的,数据库大小限制小于 2gb。

            【讨论】:

              【解决方案7】:

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

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

              【讨论】:

                【解决方案8】:

                我同意banjollity。建立隔离的开发和测试环境应该是一个高优先级。我使用的每个数据库系统要么是开源的,要么有一个免费的开发者版本,你可以安装在本地工作站上。这使您可以针对与生产相同的数据库方言进行开发,为您提供对开发数据库的完全管理员访问权限,并且比使用远程服务器更快。

                【讨论】:

                  【解决方案9】:

                  尝试使用derby。它简单便携。使用 Hibernate,您的应用程序变得灵活。测试德比,生产任何你喜欢和信任的东西。

                  【讨论】:

                    【解决方案10】:

                    我们正在创建一个正在工作的数据库测试环境。我们觉得我们必须使用带有模拟数据真实数据库管理系统。模拟 DBMS 的一个问题是 SQL 从未真正完全成为标准,因此人工测试环境必须忠实地支持我们生产数据库的方言。另一个问题是我们广泛使用列值约束、外键约束和唯一约束,并且由于人工工具可能无法实现这些,因此我们的单元测试可以通过,但我们的系统测试在第一次遇到实际情况时会失败约束。如果测试花费的时间太长,这表明实现错误,我们将调整查询(通常测试数据集与生产数据集相比是微不足道的)。

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

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

                    这样做的主要优势在于,我们可以提前发现更多错误,这些错误更容易修复,并且当开发人员狂热地尝试调试问题时,我们的真实系统测试不会受到阻碍。这意味着我们可以更快、更省力地生成更好的代码。

                    【讨论】:

                      【解决方案11】:

                      您可以使用 HSQLDB 进行内存数据库测试。启动内存数据库并在其上运行测试非常简单。
                      http://hsqldb.org/

                      【讨论】:

                        【解决方案12】:

                        jOOQ 是一个工具,除了提供 SQL 抽象外,还内置了一些小工具,例如允许模拟整个 JDBC 的 SPI。 This can work in two ways as documented in this blog post:

                        通过实现MockDataProvider SPI:

                        // 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 语法。

                        好的,通过如上所示模拟数据库,您可以“验证”语法,因为您在上面列出的 exact 版本中没有预见到的每个语法都将被任何此类模拟拒绝接近。

                        您可以实现解析 SQL (or, again, use jOOQ's) 的解析器,然后将 SQL 语句转换为您可以更容易识别并生成结果的内容。但归根结底,这只是意味着实现一个完整的数据库。

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

                        这让事情变得更加困难。如果你运行一个插入然后更新,结果显然不同于先更新,然后插入,因为更新可能会也可能不会影响插入的行。

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

                        嘲讽只会带你到这一步

                        正如piotrek 所提到的,嘲弄只会带你到这一步。当您只需要拦截一些非常知名的查询时,它在简单的情况下很有用。如果您想模拟整个系统的数据库,这是不可能的。在这种情况下,请使用实际数据库,最好是您在生产中使用的相同产品。

                        【讨论】:

                          【解决方案13】:

                          我认为我的 Acolyte 框架可以用于此类 DB 模型:https://github.com/cchantep/acolyte

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

                          【讨论】:

                            【解决方案14】:

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

                            【讨论】:

                              【解决方案15】:

                              H2 数据库

                              “许多 Java 开发人员最喜欢的集成测试数据库。”

                              (Hypersonic变成了HSQLDB,改写为H2)

                              https://blog.jooq.org/2015/08/18/jooq-tuesdays-thomas-muller-unveils-how-hsqldb-evolved-into-the-popular-h2-database/

                              【讨论】:

                                猜你喜欢
                                • 1970-01-01
                                • 2021-05-22
                                • 1970-01-01
                                • 1970-01-01
                                • 2010-09-23
                                • 1970-01-01
                                • 2012-05-09
                                • 1970-01-01
                                • 1970-01-01
                                相关资源
                                最近更新 更多