Database 什么';对数据库驱动的应用程序进行单元测试的最佳策略是什么?

Database 什么';对数据库驱动的应用程序进行单元测试的最佳策略是什么?,database,unit-testing,orm,mocking,Database,Unit Testing,Orm,Mocking,我处理的很多web应用程序都是由后端复杂程度不同的数据库驱动的。通常,有一个独立于业务和表示逻辑的层。这使得业务逻辑的单元测试相当简单;事情可以在离散的模块中实现,测试所需的任何数据都可以通过对象模拟来伪造 但是测试ORM和数据库本身总是充满问题和妥协 这些年来,我尝试了一些策略,但没有一个能让我完全满意 用已知数据加载测试数据库。对ORM运行测试并确认返回了正确的数据。这里的缺点是测试数据库必须跟上应用程序数据库中的任何模式更改,并且可能会失去同步。它还依赖于人工数据,并且可能不会暴露由于愚

我处理的很多web应用程序都是由后端复杂程度不同的数据库驱动的。通常,有一个独立于业务和表示逻辑的层。这使得业务逻辑的单元测试相当简单;事情可以在离散的模块中实现,测试所需的任何数据都可以通过对象模拟来伪造

但是测试ORM和数据库本身总是充满问题和妥协

这些年来,我尝试了一些策略,但没有一个能让我完全满意

  • 用已知数据加载测试数据库。对ORM运行测试并确认返回了正确的数据。这里的缺点是测试数据库必须跟上应用程序数据库中的任何模式更改,并且可能会失去同步。它还依赖于人工数据,并且可能不会暴露由于愚蠢的用户输入而出现的错误。最后,如果测试数据库很小,它将不会显示诸如缺少索引之类的低效率。(好的,最后一个问题并不是单元测试应该用来做的事情,但它并没有坏处。)

  • 加载生产数据库的副本并根据该副本进行测试。这里的问题是,您可能不知道在任何给定的时间生产数据库中有什么内容;如果数据随时间变化,您的测试可能需要重写

有人指出,这两种策略都依赖于特定的数据,单元测试应该只测试功能。为此,我看到了一些建议:

  • 使用模拟数据库服务器,仅检查ORM是否发送正确的查询以响应给定的方法调用

您使用了哪些策略来测试数据库驱动的应用程序(如果有的话)?什么对您最有效?

实际上,我使用了您的第一种方法,取得了一定的成功,但我认为这是一种稍微不同的方法,可以解决您的一些问题:

  • 在源代码管理中保留整个模式和用于创建它的脚本,以便任何人都可以在签出后创建当前数据库模式。此外,将示例数据保存在生成过程中加载的数据文件中。当您发现导致错误的数据时,将其添加到示例数据中,以检查错误不会再次出现

  • 使用持续集成服务器构建数据库架构、加载示例数据并运行测试。这就是我们保持测试数据库同步的方式(在每次测试运行时重建它)。尽管这要求CI服务器拥有自己专用数据库实例的访问权和所有权,但我认为每天构建3次db模式可以极大地帮助发现可能在交付之前(如果不是以后)才发现的错误。我不能说我在每次提交之前都重建了模式。有人吗?使用这种方法,你不必这么做(也许我们应该这么做,但如果有人忘记了,这没什么大不了的)

  • 对于我的团队,用户输入是在应用程序级别(而不是db)完成的,因此这是通过标准单元测试进行测试的

  • 正在加载生产数据库副本:
    这是我上一份工作中使用的方法。这是几个问题造成的巨大痛苦:

  • 从生产版本来看,副本将过时
  • 将对副本的架构进行更改,并且不会传播到生产系统。在这一点上,我们会有不同的模式。不好玩
  • 模拟数据库服务器:

    我们在我目前的工作中也这样做。在每次提交之后,我们对已注入模拟db访问器的应用程序代码执行单元测试。然后,我们每天执行三次上述完整的数据库构建。我绝对推荐这两种方法。

    我问这个问题已经很久了,但我认为没有解决这个问题的灵丹妙药

    我目前所做的是模拟DAO对象,并在内存中保留一个良好的对象集合表示,这些对象表示可能存在于数据库中的有趣的数据案例

    我看到这种方法的主要问题是,您只涉及与DAO层交互的代码,而从不测试DAO本身,根据我的经验,我发现在该层上也会发生很多错误。我还保留了一些针对数据库运行的单元测试(为了在本地使用TDD或快速测试),但这些测试从未在我的持续集成服务器上运行,因为我们没有为此目的保留数据库,我认为在CI服务器上运行的测试应该是自包含的

    我发现另一种方法非常有趣,但并不总是值得的,因为它有点费时,就是在一个嵌入式数据库上创建用于生产的相同模式,该数据库只在单元测试中运行

    尽管毫无疑问,这种方法可以提高您的覆盖率,但也有一些缺点,因为您必须尽可能接近ANSI SQL,才能使其与当前的DBMS和嵌入式替代品一起工作


    无论您认为什么与您的代码更相关,都有一些项目可以让它变得更简单,例如。

    出于以下原因,我总是对内存中的DB(HSQLDB或Derby)运行测试:

    • 它会让您思考在测试数据库中保存哪些数据以及为什么要保存这些数据。将生产数据库拖到测试系统中就意味着“我不知道我在做什么,也不知道为什么,如果出现故障,那不是我!!”;)
    • 它可以确保在一个新的地方(例如,当我们需要从生产中复制一个bug时)轻松地重新创建数据库
    • 它极大地提高了DDL文件的质量
    一旦测试开始,内存中的数据库就会加载新的数据,在大多数测试之后,我调用ROLLBACK来保持它的稳定始终保持测试数据库中的数据稳定!如果数据一直在变化
    @Test
    public void savedCommentCanBeRead() {
        // Builder is needed to declaratively specify the entity with all attributes relevant
        // for this specific test
        // Missing attributes are generated with reasonable values
        // factory's responsibility is to create entity (and all entities required by it
        //  in our example Author) in the DB
        Post post = factory.create(PostBuilder.post());
    
        Comment comment = CommentBuilder.comment().forPost(post).build();
    
        sut.save(comment);
    
        Comment savedComment = sut.get(comment.getId());
    
        // this checks fields that are directly stored
        assertThat(saveComment, fieldwiseEqualTo(comment));
        // if there are some fields that are generated during save check them separately
        assertThat(saveComment.getGeneratedField(), equalTo(expectedValue));        
    }