集成测试所需的数据库数据;由 API 调用或使用导入的数据创建?
Posted
技术标签:
【中文标题】集成测试所需的数据库数据;由 API 调用或使用导入的数据创建?【英文标题】:Database data needed in integration tests; created by API calls or using imported data? 【发布时间】:2010-10-03 17:27:06 【问题描述】:这个问题或多或少与编程语言无关。但是,由于这些天我主要使用 Java,因此我将从中提取示例。我也在考虑 OOP 的情况,所以如果你想测试一个方法,你需要一个方法类的实例。
unit tests 的核心规则是它们应该是自治的,这可以通过将一个类与其依赖项隔离来实现。有几种方法可以做到这一点,这取决于您是否使用 IoC 注入依赖项(在 Java 世界中,我们有 Spring、EJB3 和其他提供注入功能的框架/平台)和/或您是否模拟对象(对于 Java你有 JMock 和 EasyMock) 来将被测试的类与其依赖项分开。
如果我们需要测试不同类*中的方法组并查看它们是否很好地集成,我们编写integration tests。这是我的问题!
至少在 Web 应用程序中,状态通常保存在数据库中。我们可以使用与单元测试相同的工具来实现与数据库的独立性。但是以我的拙见,我认为在某些情况下,不使用数据库进行集成测试是在嘲笑太多(但可以不同意;根本不使用数据库也是一个有效的答案,因为它使问题变得无关紧要)。 当您使用数据库进行集成测试时,如何用数据填充该数据库?我可以看到两种方法: 存储集成测试的数据库内容并在开始测试之前加载它。如果它存储为 SQL 转储、数据库文件、XML 或其他内容,将会很有趣。 通过 API 调用创建必要的数据库结构。这些调用可能会在您的测试代码中拆分为多个方法,并且这些方法中的每一个可能失败。它可以被视为您的集成测试依赖于其他测试。您如何确定测试所需的数据库数据在您需要时是否存在?你为什么选择你选择的方法?
请提供带有动机的答案,因为有趣的部分在于动机。请记住,只是说“这是最佳实践!”不是一个真正的动机,它只是重复你读过或从某人那里听到的东西。对于这种情况,请解释为什么这是最佳做法。
*在我的单元测试定义中,我在同一类的(相同或其他)实例中包含一个调用其他方法的方法,即使它在技术上可能不正确。请随时纠正我,但让我们把它作为一个附带问题。
【问题讨论】:
我删除了发布为 CW 的同一个问题,现在在没有 CW 的情况下添加了它。我收到了一条有用的评论,解释说 CW 会干扰声誉积分系统,所以现在它不会了,你会得到所有你可以通过回答获得的奖励! :-) 我开始在这方面提供小额赏金,因为我想要更多建议。不要吝啬动机! :-) 一周过去后,我会选择票数最高的答案作为我接受的答案,我也在投票,但我的投票与你的投票相同。 【参考方案1】:我更喜欢使用 API 调用创建测试数据。
在测试开始时,您创建一个空数据库(在内存中或与生产中使用的相同),运行安装脚本对其进行初始化,然后创建数据库使用的任何测试数据。例如,可以使用Object Mother 模式组织测试数据的创建,以便可以在许多测试中重复使用相同的数据,可能会有细微的变化。
您希望在每次测试之前使数据库处于已知状态,以便进行可重现的测试而不会产生副作用。因此,当一个测试结束时,您应该删除测试数据库或回滚事务,以便下一个测试可以始终以相同的方式重新创建测试数据,而不管之前的测试是通过还是失败。
我会避免导入数据库转储(或类似的)的原因是它将测试数据与数据库模式耦合。当数据库架构发生变化时,您还需要更改或重新创建测试数据,这可能需要手动操作。
如果测试数据是在代码中指定的,您将拥有 IDE 重构工具的强大功能。当您进行影响数据库架构的更改时,它可能还会影响 API 调用,因此您无论如何都需要使用 API 重构代码。通过几乎相同的努力,您还可以重构测试数据的创建——特别是如果重构可以自动化(重命名、引入参数等)。但如果测试依赖于数据库转储,除了重构使用 API 的代码之外,您还需要手动重构数据库转储。
与集成测试数据库相关的另一件事是测试从以前的数据库模式升级是否正常工作。为此,您可能想阅读本书Refactoring Databases: Evolutionary Database Design 或这篇文章:http://martinfowler.com/articles/evodb.html
【讨论】:
惊人的答案。但是使用现代工具,您不认为 DDL 和 DML 都可以轻松更改吗? Jetbrains datagrip(例如)在编写查询时具有出色的智能感知能力。因此,如果数据模型发生更改,您只需更改 schema.sql,然后更改相应的 data.sql。既然有 SQL,为什么还要使用 API(一种编程语言)来生成数据?就像使用错误的工具完成工作一样。在现代你怎么看这件事?与简单地编写 sql 文件相比,花时间在基于 API 的测试生成上是否值得?【参考方案2】:在集成测试中,您需要使用真实的数据库进行测试,因为您必须验证您的应用程序实际上可以与数据库对话。将数据库隔离为依赖项意味着您推迟了对数据库是否正确部署、您的架构是否符合预期以及您的应用程序是否配置了正确的连接字符串的真正测试。当您部署到生产环境时,您不希望发现这些问题。
您还想使用预先创建的数据集和空数据集进行测试。您需要测试您的应用程序从仅使用默认初始数据的空数据库开始并开始创建和填充数据的路径以及针对您要测试的特定条件的明确定义的数据集,例如压力、性能和以此类推。
另外,请确保您的数据库在每个状态之前都处于众所周知的状态。您不希望在集成测试之间存在依赖关系。
【讨论】:
【参考方案3】:为什么将这两种方法定义为排他性的?
我看不到任何可行的论据 不使用预先存在的数据集,尤其是具有 造成过去的问题。
我不能 查看 not 的任何可行论据 以编程方式扩展该数据 所有可能的条件 你可以想象导致问题甚至是 用于集成的随机数据位 测试。
在现代agile approaches 中,单元测试是每次运行相同测试真正重要的地方。这是因为单元测试的目的不是发现错误,而是在开发应用程序时保留应用程序的功能,允许开发人员根据需要进行重构。
另一方面,集成测试旨在发现您没有预料到的错误。在我看来,每次运行 一些 不同的数据甚至会很好。如果你失败了,你只需要确保你的测试保留失败的数据。请记住,在正式的集成测试中,除了错误修复之外,应用程序本身将被冻结,因此您的测试可以更改以测试最大可能数量和种类的错误。在集成中,您可以而且应该将厨房水槽扔给应用程序。
当然,正如其他人所指出的,这一切自然取决于您正在开发的应用程序类型以及您所在的组织类型等。
【讨论】:
【参考方案4】:我使用DBUnit 对数据库中的记录进行快照并以 XML 格式存储它们。然后我的单元测试(当它们需要数据库时我们称之为集成测试),可以在每个测试开始时从 XML 文件中擦除和恢复。
我不确定这是否值得努力。一个问题是对其他表的依赖性。我们单独留下了静态引用表,并构建了一些工具来检测和提取所有子表以及请求的记录。我阅读了有人建议禁用集成测试数据库中的所有外键。这将使准备数据变得更容易,但您不再检查测试中的任何引用完整性问题。
另一个问题是数据库架构更改。我们编写了一些工具,可以为自拍摄快照以来添加的列添加默认值。
显然这些测试比纯单元测试慢。
当您尝试测试一些难以为单个类编写单元测试的遗留代码时,这种方法可能是值得的。
【讨论】:
【参考方案5】:听起来你的问题实际上是两个问题。您应该从测试中排除数据库吗?当你做一个数据库时,那么你应该如何生成数据库中的数据呢?
如果可能,我更喜欢使用实际的数据库。面对实际数据库时,CRUD 类中的查询(用 SQL、HQL 等编写)经常会返回令人惊讶的结果。最好尽早解决这些问题。开发人员通常会为 CRUD 编写非常精简的单元测试;只测试最良性的病例。使用实际数据库进行测试可以测试您甚至可能没有意识到的各种极端情况。
话虽如此,可能还有其他问题。每次测试后,您都希望将数据库恢复到已知状态。我目前的工作是通过执行所有 DROP 语句然后从头开始完全重新创建所有表来核对数据库。这在 Oracle 上非常慢,但如果您使用像 HSQLDB 这样的内存数据库,则速度会非常快。当我们需要清除 Oracle 特定问题时,我们只需更改数据库 URL 和驱动程序属性,然后针对 Oracle 运行。如果您没有这种数据库可移植性,那么 Oracle 还具有某种数据库快照功能,可以专门用于此目的;将整个数据库回滚到以前的某个状态。我确定其他数据库有什么。
根据您的数据库中的数据类型,API 或加载方法可能会更好或更差。当您拥有具有许多关系的高度结构化数据时,API 将使您的生活更轻松,因为我可以明确数据之间的关系。创建测试数据集时,您将更难犯错误。正如其他海报所提到的,重构工具可以自动处理对数据结构的一些更改。我经常发现将 API 生成的测试数据视为一个场景很有用。当用户/系统完成步骤 X、Y Z 然后测试将从那里开始。可以实现这些状态,因为您可以编写一个程序来调用您的用户将使用的相同 API。
当您需要大量数据时,加载数据变得更加重要,您的数据之间的关系很少,或者数据存在无法使用 API 或标准关系机制表达的一致性。我团队的一项工作是为大型网络数据包检测系统编写报告应用程序。当时的数据量相当大。为了触发有用的测试用例子集,我们确实需要嗅探器生成的测试数据。这样,关于一个协议的信息之间的相关性将与关于另一个协议的信息相关联。在 API 中很难捕捉到这一点。
大多数数据库都有工具来导入和导出表格的分隔文本文件。但通常你只想要它们的子集;使使用数据转储变得更加复杂。在我目前的工作中,我们需要转储一些由 Matlab 程序生成并存储在数据库中的实际数据。我们有工具可以转储数据库数据的子集,然后将其与“基本事实”进行比较以进行测试。我们的提取工具似乎在不断修改。
【讨论】:
【参考方案6】:根据我需要测试的内容,我两者都做:
我从 SQL 脚本或数据库转储中导入静态测试数据。此数据用于对象加载(反序列化或对象映射)和 SQL 查询测试(当我想知道代码是否会返回正确的结果时)。
另外,我通常有一些主干数据(配置、名称查找表的值等)。这些也在此步骤中加载。请注意,此加载是单个测试(以及从头开始创建数据库)。
当我有修改数据库(对象 -> 数据库)的代码时,我通常会针对一个活动数据库(在内存中或某处的测试实例中)运行它。这是为了确保代码有效;不要创建任何大量的行。测试后,我回滚事务(遵循测试不能修改全局状态的规则)。
当然,规则也有例外:
我还在性能测试中创建了大量行。 有时,我必须提交单元测试的结果(否则,测试会变得太大)。【讨论】:
【参考方案7】:我一般使用 SQL 脚本来填充您讨论的场景中的数据。
它简单明了,非常容易重复。
【讨论】:
但是当实体发生变化时,您还必须更改 SQL 脚本中的数据。为什么这对你来说不是问题? 我正在使用内存数据库 SQL Lite。 Propel ORM for php 允许您转储模式和数据。我只在测试中进行了两次调用,它创建了数据库并从先前导出的文件中导入了一组新数据。导出转储只需不到 30 秒。【参考方案8】:这可能无法回答您的所有问题(如果有的话),但我在一个项目中决定对数据库进行单元测试。在我的情况下,我觉得数据库结构也需要测试,即我的数据库设计是否提供了应用程序所需的内容。在项目后期当我觉得数据库结构稳定时,我可能会远离这个。
为了生成数据,我决定创建一个用“随机”数据填充数据库的外部应用程序,我创建了一个人名和公司名生成器等。
在外部程序中这样做的原因是: 1.我可以通过测试修改数据重新运行测试,即确保我的测试能够运行多次并且测试所做的数据修改是有效的修改。 2. 如果需要,我可以清理数据库并重新开始。
我同意这种方法存在一些失败点,但就我而言,例如人员生成是业务逻辑的一部分,为测试生成数据实际上也是在测试该部分。
【讨论】:
【参考方案9】:我们的团队最近遇到了同样的问题。
之前,我们使用 specflow 进行集成测试。使用 specflow,QA 可以编写每个测试用例,在其中将必要的测试数据填充到 DB。
现在,QA 想使用 postman 来测试 API,他们如何填充数据?一种解决方案是创建用于填充它们的 API。另一个是将 Prod 的历史数据同步到测试环境。
一旦我们尝试不同的解决方案并决定采用哪一个,我会更新我的答案。
【讨论】:
以上是关于集成测试所需的数据库数据;由 API 调用或使用导入的数据创建?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 api 请求标头中插入 api 身份验证所需的 jwt 令牌?
Marvel API 和 Insomnia(或 Postman):如何传递所需的哈希值?
如何确定特定 win32 api 调用所需的 windows 库?
使用新的 NSKeyedUnarchiver API 取消归档数据,但未能满足所需的 unarchivedObjectOfClass