61

这个问题或多或少与编程语言无关。但是,由于这些天我主要使用 Java,因此我将从中提取示例。我也在考虑 OOP 案例,所以如果你想测试一个方法,你需要一个该方法类的实例。

单元测试核心规则是它们应该是自治的,这可以通过将一个类与其依赖项隔离来实现。有几种方法可以做到这一点,这取决于您是否使用IoC注入依赖项(在 Java 世界中,我们有 Spring、EJB3 和其他提供注入功能的框架/平台)和/或您是否模拟对象(对于 Java,您有JMockEasyMock)将被测试的类与其依赖项分开。

如果我们需要测试不同类*中的方法组并看到它们很好地集成,我们编写集成测试。这是我的问题!

  • 至少在 Web 应用程序中,状态通常被持久化到数据库中。我们可以使用与单元测试相同的工具来实现与数据库的独立性。但是以我的拙见,我认为在某些情况下,不使用数据库进行集成测试是在嘲笑太多(但可以不同意;根本不使用数据库也是一个有效的答案,因为它使问题变得无关紧要)。
  • 当您使用数据库进行集成测试时,如何用数据填充该数据库?我可以看到两种方法:
    • 存储集成测试的数据库内容并在开始测试之前加载它。如果它被存储为 SQL 转储、数据库文件、XML 或其他东西会很有趣。
    • 通过 API 调用创建必要的数据库结构。这些调用可能会在您的测试代码中拆分为几个方法,并且这些方法中的每一个都可能失败。它可以被视为您的集成测试依赖于其他测试。

您如何确定测试所需的数据库数据在您需要时是否存在?你为什么选择你选择的方法?

请提供带有动机的答案,因为有趣的部分在于动机。请记住,只是说“这是最佳实践!” 不是真正的动机,它只是重复你读过或从某人那里听到的东西。对于这种情况,请解释为什么它是最佳实践。

*在我的单元测试定义中,我在同一类的(相同或其他)实例中包含了一种调用其他方法的方法,即使它在技术上可能不正确。随时纠正我,但让我们把它作为一个附带问题。

4

9 回答 9

55

我更喜欢使用 API 调用创建测试数据。

在测试开始时,您创建一个空数据库(内存中或生产中使用的相同),运行安装脚本来初始化它,然后创建数据库使用的任何测试数据。例如,可以使用Object Mother模式来组织测试数据的创建,以便可以在许多测试中重复使用相同的数据,可能会有微小的变化。

您希望在每次测试之前使数据库处于已知状态,以便进行可重现的测试而不会产生副作用。因此,当测试结束时,您应该删除测试数据库或回滚事务,以便下一个测试可以始终以相同的方式重新创建测试数据,而不管之前的测试是通过还是失败。

我会避免导入数据库转储(或类似的)的原因是它将测试数据与数据库模式耦合。当数据库架构更改时,您还需要更改或重新创建测试数据,这可能需要手动工作。

如果测试数据是在代码中指定的,那么您将拥有 IDE 重构工具的强大功能。当您进行影响数据库架构的更改时,它可能还会影响 API 调用,因此您无论如何都需要使用 API 重构代码。通过几乎相同的努力,您还可以重构测试数据的创建 - 特别是如果重构可以自动化(重命名、引入参数等)。但是,如果测试依赖于数据库转储,除了重构使用 API 的代码之外,您还需要手动重构数据库转储。

与数据库集成测试相关的另一件事是测试从以前的数据库模式升级是否正常工作。为此,您可能想阅读《重构数据库:进化数据库设计》一书或这篇文章:http ://martinfowler.com/articles/evodb.html

于 2009-02-12T19:13:18.597 回答
6

在集成测试中,您需要使用真实数据库进行测试,因为您必须验证您的应用程序实际上可以与数据库对话。将数据库隔离为依赖项意味着您推迟了对数据库是否正确部署、您的架构是否符合预期以及您的应用程序是否配置了正确的连接字符串的真正测试。当您部署到生产环境时,您不想发现这些问题。

您还想使用预先创建的数据集和空数据集进行测试。您需要测试您的应用程序从仅使用默认初始数据的空数据库开始并开始创建和填充数据的路径以及针对您要测试的特定条件的明确定义的数据集,例如压力、性能和很快。

另外,请确保您在每个状态之前都使数据库处于众所周知的状态。您不希望在集成测试之间存在依赖关系。

于 2009-02-16T08:23:48.677 回答
5

为什么将这两种方法定义为排他性的?

  • 对于不使用预先存在的数据集,尤其是过去引起问题的特定数据, 我看不出任何可行的论据 。

  • 我看不到任何可行的论据,即 以您可以想象的所有可能导致问题的条件,甚至是一些用于集成测试的随机数据,以编程方式扩展该数据。

在现代敏捷方法中,单元测试是每次运行相同测试真正重要的地方。这是因为单元测试的目的不是发现错误,而是在开发应用程序时保留应用程序的功能,允许开发人员根据需要进行重构。

另一方面,集成测试旨在发现您没有预料到的错误。在我看来,每次运行一些不同的数据甚至会很好。如果你失败了,你只需要确保你的测试保留失败的数据。请记住,在正式的集成测试中,除了错误修复之外,应用程序本身将被冻结,因此您可以更改测试以测试最大可能数量和种类的错误。在集成中,您可以而且应该将厨房水槽扔给应用程序。

当然,正如其他人所指出的,所有这一切自然取决于您正在开发的应用程序类型以及您所在的组织类型等。

于 2009-02-16T05:51:49.353 回答
4

我使用DBUnit对数据库中的记录进行快照并以 XML 格式存储它们。然后我的单元测试(当它们需要数据库时,我们称它们为集成测试),可以在每次测试开始时从 XML 文件中擦除和恢复。

我不确定这是否值得付出努力。一个问题是对其他表的依赖性。我们单独留下了静态引用表,并构建了一些工具来检测和提取所有子表以及请求的记录。我阅读了有人建议禁用集成测试数据库中的所有外键。这将使准备数据变得更容易,但您不再检查测试中的任何引用完整性问题。

另一个问题是数据库架构更改。我们编写了一些工具,可以为自拍摄快照以来添加的列添加默认值。

显然,这些测试比纯单元测试要慢得多。

当您尝试测试一些很难为单个类编写单元测试的遗留代码时,这种方法可能是值得的。

于 2009-02-11T23:18:22.777 回答
4

听起来你的问题实际上是两个问题。您应该从测试中排除数据库吗?当你做一个数据库时,那么你应该如何生成数据库中的数据呢?

如果可能,我更喜欢使用实际的数据库。当面对实际数据库时,CRUD 类中的查询(用 SQL、HQL 等编写)通常会返回令人惊讶的结果。最好尽早解决这些问题。开发人员通常会为 CRUD 编写非常精简的单元测试;只测试最良性的病例。使用实际数据库进行测试可以测试您甚至可能没有意识到的各种极端情况。

话虽如此,可能还有其他问题。每次测试后,您都希望将数据库恢复到已知状态。我目前的工作是通过执行所有 DROP 语句然后从头开始完全重新创建所有表来对数据库进行核对。这在 Oracle 上非常慢,但如果您使用像 HSQLDB 这样的内存数据库,则速度会非常快。当我们需要清除 Oracle 特定问题时,我们只需更改数据库 URL 和驱动程序属性,然后针对 Oracle 运行。如果您没有这种数据库可移植性,那么 Oracle 还具有某种数据库快照功能,可以专门用于此目的;将整个数据库回滚到以前的某个状态。我确定其他数据库有什么。

根据数据库中的数据类型,API 或加载方法可能会更好或更差。当您拥有具有许多关系的高度结构化的数据时,API 将使您的生活更轻松,因为我将数据之间的关系明确化。创建测试数据集时,您将更难犯错误。正如其他海报所提到的,重构工具可以自动处理对数据结构的一些更改。我经常发现将 API 生成的测试数据视为一个场景很有用。当用户/系统完成步骤 X、YZ 然后测试将从那里开始。可以实现这些状态,因为您可以编写一个程序来调用您的用户将使用的相同 API。

当您需要大量数据时,加载数据变得更加重要,您的数据之间的关系很少,或者数据中存在无法使用 API 或标准关系机制表达的一致性。我团队的一项工作是为大型网络数据包检测系统编写报告应用程序。当时的数据量相当大。为了触发有用的测试用例子集,我们确实需要嗅探器生成的测试数据。这样,关于一个协议的信息之间的相关性将与关于另一个协议的信息相关联。在 API 中很难捕捉到这一点。

大多数数据库都有工具来导入和导出表格的分隔文本文件。但通常你只想要它们的子集;使使用数据转储变得更加复杂。在我目前的工作中,我们需要转储一些由 Matlab 程序生成并存储在数据库中的实际数据。我们有工具可以转储数据库数据的子集,然后将其与“基本事实”进行比较以进行测试。似乎我们的提取工具正在不断修改。

于 2009-02-13T20:05:35.780 回答
3

我两者都做,具体取决于我需要测试的内容:

  • 我从 SQL 脚本或数据库转储中导入静态测试数据。这些数据用于对象加载(反序列化或对象映射)和 SQL 查询测试(当我想知道代码是否会返回正确的结果时)。

    另外,我通常有一些主干数据(配置、名称查找表的值等)。这些也在此步骤中加载。请注意,此加载是单个测试(以及从头开始创建数据库)。

  • 当我有修改数据库(对象-> 数据库)的代码时,我通常针对一个活的数据库(在内存中或某处的测试实例中)运行它。这是为了确保代码有效;不要创建任何大量的行。测试后,我回滚事务(遵循测试不得修改全局状态的规则)。

当然,规则也有例外:

  • 我还在性能测试中创建了大量行。
  • 有时,我必须提交单元测试的结果(否则,测试会变得太大)。
于 2009-01-27T11:09:14.933 回答
2

我一般使用 SQL 脚本来填充您讨论的场景中的数据。

这是直截了当的,非常容易重复。

于 2009-01-27T10:18:35.487 回答
0

这可能无法回答您的所有问题(如果有的话),但我在一个项目中决定对数据库进行单元测试。在我的情况下,我觉得数据库结构也需要测试,即我的数据库设计是否提供了应用程序所需的内容。在项目后期当我觉得数据库结构稳定时,我可能会远离这个。

为了生成数据,我决定创建一个用“随机”数据填充数据库的外部应用程序,我创建了一个人名和公司名生成器等。

在外部程序中这样做的原因是: 1. 我可以通过测试修改的数据重新运行测试,即确保我的测试能够运行多次并且测试所做的数据修改是有效的修改。2. 如果需要,我可以清理数据库并重新开始。

我同意这种方法存在失败点,但在我的情况下,因为例如人员生成是为测试生成数据的业务逻辑的一部分,实际上也在测试该部分。

于 2009-01-27T10:19:47.047 回答
0

我们的团队最近面临同样的问题。

之前,我们使用 specflow 进行集成测试。使用 specflow,QA 可以编写每个测试用例,在其中将必要的测试数据填充到 DB。

现在,QA 想使用 postman 来测试 API,他们如何填充数据?一种解决方案是创建用于填充它们的 API。另一个是将 Prod 的历史数据同步到测试环境。

一旦我们尝试不同的解决方案并决定选择哪一个,将更新我的答案。

于 2021-02-16T21:55:16.220 回答