更改数据库模式和单元测试

Posted

技术标签:

【中文标题】更改数据库模式和单元测试【英文标题】:Changing Database schemas & unit tests 【发布时间】:2010-11-23 16:19:57 【问题描述】:

     在我们开始之前,我知道有相当一部分人认为命中数据库的测试不是“单元测试”。也许“集成测试”会是一个更好的名字。无论哪种方式,开发人员都会对数据库进行测试。

     为了启用单元测试,我有一个开发人员本地数据库,我清除它并在每次测试开始时使用dbUnit 填充一组已知数据。这一切都很好,直到测试使用的表以某种方式发生变化,我必须手动更新所有 XML 数据集。这是一种痛苦。我认为其他人一定遇到了同样的问题,并希望找到一个很好的解决方案。那么对于需要填充数据库的测试,您使用什么以及如何处理表定义的变化? (虽然我使用 Java,但我对使用不同技术的解决方案持开放态度。)

编辑: 澄清一点。我有一个人为的测试,例如:

void testLoadRevision() 
    database.clear(); // Clears every table dbUnit knows about.
    database.load("load/trevision.xml", "load/tissue.xml");
    SomeDatabaseThingie subject = new SomeDatabaseThingie(databaseProvider);
    Revision actual = subject.load();
    assert(actual, expected);

我有两个表 - tRevision 和 tIssue。加载的修订版使用来自 tIssue 的少量数据。后来 tIssue 获得了一个修订版不关心的新字段。由于新字段是“非空”并且没有合理的默认值,因此该测试将失败,因为 tIssue.xml 将无效。

通过像这样的小改动,编辑问题并不难。但是,当 XML 文件的数量随着每个流程开始激增时,它就会变成大量的工作。

干杯, 米尔克

【问题讨论】:

【参考方案1】:

嗯,在我看来,这是一个结合已经存在的东西的问题。

上述场景:

    编写数据库迁移 应用数据库迁移(手动或在测试运行开始时自动) 注意您的测试因违反约束而中断(非空)

您可以扩展它,使您成为一个执行以下操作的小程序:

    使用 DbUnit XML 填充数据库 应用数据库迁移 在 DbUnit XML(以及可选的 DTD)中就地提取数据库的内容(参见 DbUnit 主页 -> DbUnit 常见问题解答 -> 如何从我的数据库中提取平面 XML 数据集?) 将更新后的 DbUnit XML(和 DTD)检查到源代码管理中。

为了申请迁移,我衷心推荐Flyway。它支持 Sql(带占位符替换)和基于 Java 的迁移。然后,您可以使用 Maven 插件或使用 API 以编程方式应用迁移。后者非常适合这种情况。

那么完整的工作流程就变成了:

    编写您的数据库迁移 执行您的 DbUnitXmlDtdUpdater 程序 看着您的单元测试通过

快乐的日子,

阿克塞尔

免责声明:我是 Flyway 的开发人员之一。

【讨论】:

乔纳斯,我提出的答案肯定不是一刀切。让我来谈谈你的观点: 生成值:真正的问题是,你真的想要在测试数据中生成值吗?我倾向于保持我的测试数据尽可能稳定(对于可重复的测试),尽可能避免生成值。如果您必须使用生成的值,那么 DbUnit 的 DefaultColumnFilter 可以提供帮助。对于触发器,需要逐案分析以查看是否或如何补偿其影响。将数据集拆分为多个文件意味着处理 DbUnit 的 DefaultTableFilter 来划分测试数据。【参考方案2】:

我认为这个问题的答案分为两个阶段:

架构只有一个权威定义

对于数据库的外观应该只有一个定义。在正常情况下,我更喜欢使用 SQL DDL 脚本来指定数据库的架构。

单元测试应该使用与应用程序相同的数据库模式的权威定义,并且它应该在测试运行之前基于该定义创建数据库并在测试运行之后再次完全删除它.

也就是说,工具可能与架构不同步,您需要手动更新工具生成的内容。例如,我使用 .NET 的实体框架,它根据数据库模式自动生成类。当我更改架构时,我需要手动告诉我的工具更新这些类。这很痛苦,但我不知道有什么办法可以解决,除非工具支持自动化。

每个测试都应该从空数据开始

每个测试都应该从没有任何数据的数据库开始。每个测试都应填充执行测试所需的数据,完成后,应再次清理数据库。

您目前正在做的事情听起来像是一种称为 General Fixture 的反模式,您尝试在其中预加载一组数据,这些数据代表尽可能广泛的一组场景。但是,这使得测试互斥条件变得非常困难,并且如果您在某些测试中修改此预加载的数据,也可能导致测试相互依赖。

这在优秀的书xUnit Test Patterns中有很好的解释。

【讨论】:

感谢您的回复马克。除了在每次测试后重建数据库模式并在最后清理数据库¹之外,您正在描述我的工作。在 每个 测试之前,我清理构建仅用于该测试的数据。当这个测试使用的数据(保存在 XML 文档中)与数据库模式不同步时,问题就出现了(我们有大量的应用程序都访问同一个数据库。我只负责其中的几个最终读取我无法控制的应用程序生成的一些数据。) 1 - 让测试结束时的数据对调试有用。【参考方案3】:

我遇到了同样的问题,即 dbunit xml 平面文件在需要更改数据的数据库架构演变时运行不同步(即使对于像添加强制列这样简单的事情)。

虽然使用一些手写脚本转换所有 xml 文件是一种选择,但我仍然认为应该在不同的抽象级别上解决这个问题,更类似于处理实时数据的方式:进化数据库设计。

数据库迁移工具已经了解 delta 脚本,因此拥有一种 dbunit 适配器会很棒。

如果发现以下涵盖该问题的博客条目: http://blog.liquibase.org/2007/06/unit-testing-the-database-access-layer.html

要解决保持测试数据定义与架构不同步的问题,您需要将测试数据与数据库一起构建,以便通过在最初创建后进行的数据库重构对其进行修改. [..] 通过在您的数据库更改中包含测试数据,数据会自动以与生产数据相同的方式保存。在每个方法的数据集上使用这种技术还具有性能更好的优势,因为数据只插入一次,...

但添加了自己:

但它的缺点是您需要在一个地方处理任何方法所需的所有测试数据。

...我猜这对于更复杂的场景是不可能的。他继续说:

为了促进这项技术,我在 LiquiBase 中构建了执行上下文的概念,这样您就可以标记测试数据更改并仅将它们应用到运行单元测试的环境中。到目前为止,我对结果感到满意。当数据库架构与代码预期的内容之间存在差异时,或者当我的 SQL 中存在错误并且我没有因数据库重构而丢失任何测试时,测试会失败。

这是链接:www.liquibase.org/manual/contexts 但这至少不是我想要的,虽然我可以将我的测试数据暴露给数据库迁移工具,但我仍然喜欢让它非常接近数据库测试。

有人想吗?

【讨论】:

Axel,虽然 Flyway 看起来很有希望,但我不确定第 3 步是否可行:将数据库内容反馈给现有的数据库单元 testdata 似乎很复杂,如果你不想让它们膨胀,因为你必须注意 - 过滤生成的值(ID、触发器等) - 通过将替换值更改回占位符名称来将 ReplacementDataSets 转换回来,但 - 最值得注意的是 - 如果测试用例和负责的测试数据之间的关系不是 1 :1,即输入来自不同的xml数据集,形成testdata的组合,你必须更新不同的xml文件。 对我自己的注意:质疑你想要做什么:根本不要使用 dbunit xml 文件,而是让测试通过漂亮和流畅的 java 工厂调用来表达他们需要的东西,而不是依赖分散的xml 文件包含测试数据,甚至更糟糕的是,在不同的测试中重复使用,因此以后根本无法知道为什么要使用特定数据集 我一直在玩基于半流利代码的 dbUnit 替代方案(dbUnit.NET 不太适合我)。数据集很小的时候很好,可惜数据集通常会膨胀很多(数据集在协议上有一个 FK,在客户端有一个 FK 等),然后在代码中要做的事情太多了。

以上是关于更改数据库模式和单元测试的主要内容,如果未能解决你的问题,请参考以下文章

如何测试我的数据是不是更改? (Vue JS、Jest、单元测试)

ServiceStack - 检查单元测试中的 WSDL 更改

SpyOn TypeORM 存储库更改单元测试 NestJS 的返回值

设计模式之美——单元测试和代码可测性

设计模式之美——单元测试和代码可测性

Chaincode调试 —— 开发者模式和单元测试