1

我们正在构建一种产品,允许用户创建自定义数据库并将数据存储在这些数据库(WebApp)中。

我们测试前端(coffeescript)的问题是每个测试都应该是原子的,但这需要设置一个数据库来查看该数据库中的项目是否可以创建和持久化,或者查看数据库中的更改如何影响项目。

本质上,问题在于进入项目测试所需的设置代码基本上设置了一个新数据库,因此等于测试设置新数据库的代码。

有两种方法,我们很难使用:

1)使用每组测试创建和拆除一个新数据库

  • (+) Sorta Atomic(如果设置数据库失败,仍然会失败)
  • (-) 执行需要大量时间
  • (-) 大量的周边代码
  • (-) 无法探索创造的环境
  • (-) 错误混乱,一切都失败了

2)逐步进行设置作为单独的测试,相互依赖,在测试开始时进行清理例程

  • (+) 可以通过 UI 访问创建的环境(不会自动拆除)
  • (+) 逐步测试,减少整体/重复代码
  • (-) 测试相互依赖(混乱)
  • (-) 整体有些凌乱

因此,我们想知道测试应该是原子的黄金法则在这样一个动态的环境中是否有意义?

4

2 回答 2

2

如果您正在与数据库交谈,那么您不是在进行原子测试。

您需要模拟数据库接口并改为与模拟对话。这将很快,并且您将能够使用模拟来引入使用真实数据库难以实现的错误。

于 2013-03-12T08:37:36.437 回答
2

基本上,您所说的是集成测试。这些与单元测试不同。集成测试的示例是自动化 UI 测试或编码 UI 测试。在我参与的大多数项目中,我们都有这两种类型的测试,我强烈建议您在项目中也同时使用这两种类型。

这两个测试背后的理念略有不同。

  • 单元测试旨在测试孤立的功能。
  • 它们应该非常快。
  • 开发人员应该能够在合理的时间内在他们的机器上运行它们。

这种哲学有各种后果。

  • 因为单元测试是测试一个孤立的功能,你应该使用模拟和存根来隔离环境的其余部分,只关注微小的功能。
  • 在编写这些测试时,隔离有助于您的“设计思维” 。事实上,这就是单元测试需要快速的原因,因为作为设计和重新设计过程的一部分,开发人员正在积极且不断地更改代码和单元测试。设置、更改和运行单元测试的开销应该非常低。我应该能够忽略除我试图解决的问题之外的所有内容,并快速迭代和重申我的设计和测试。这就是 TDD 背后的理念,它声称可以帮助编写好的可测试代码。如果您花费很长时间尝试设置过于复杂的单元测试,那么您必须开始重新考虑您的设计。
  • 快速的特性意味着您可以将这些作为持续集成构建的一部分运行。
  • 缺点是因为您是在孤立地测试每个功能,所以您不知道它们是否会作为一个整体一起工作。每次编写模拟程序时,您都在隐含地假设系统的其余部分如何工作,并且系统的其余部分当前正在按预期工作(即,在您的部署或运行​​过程中没有其他任何东西被破坏或修补操作系统等)

集成测试旨在从头到尾测试功能。您尽量不要模拟或隔离系统的任何部分。

这种哲学又产生了各种后果。请注意,集成测试不需要很快。

  • 集成测试,就其本质而言,需要在您的完整部署之后运行(与单元测试相反,单元测试可以在您的代码编译后立即运行)。
  • 因为它们需要更长的时间,所以您不会将它们作为 CI 环境的一部分运行,但您仍然需要定期运行它们。我们通常将它们作为夜间构建的一部分运行。或者你可以每天运行两次等。
  • 因为集成测试对整个系统采用黑盒方法,所以它并不能真正帮助您“设计思维”来了解如何实际构建系统。但它确实有助于您思考整个系统的规格。即系统应该做什么,而不是它应该如何做某事。

请注意,在这两种情况下,原子测试规则仍然适用。每个测试都与其他测试不同。这样,当测试失败时,您可以确定导致它失败的所有条件并专注于仅修复它。只是集成测试尽可能多地触及系统的部分。

给你一个关于我们当前项目的例子。

假设我们需要编写一些功能,要求我们向数据库添加一个新表,并将其通过所有层以在 UI 中显示。

我们首先创建业务逻辑类、域类、编写适当的 Web 服务、构建视图模型、修改数据库等。在执行这些操作时,我们编写单元测试来测试我们当前正在编写的代码。因此,在构建业务逻辑类时,我们会模拟其他所有内容以确保该类中的逻辑有效(例如,60 岁以上的客户可以获得 50% 的汽车保险折扣等)

一旦我们这样做了,我们现在需要更新我们的部署脚本/包等以便能够部署它。即更新数据库创建 SQL 脚本和数据库更改 SQL 脚本等(在您的情况下,这将是一个复杂的过程)。

现在我们编写集成测试。在这种情况下,我们可能会从 SQL Server 测试到 Web 服务。有一个 SQL 集成测试基类,其中包含每个测试的设置和拆卸方法。在设置中,我们使用我们的 sql 部署脚本创建了一个全新的数据库。每个测试还指定一个测试数据 sql 脚本。因此,例如,这个测试数据脚本可能会在年龄为 70 岁的客户表中插入一条新记录。我们将此脚本作为测试“安排”的一部分运行。然后进行 Web 服务调用以搜索 60 岁以上的客户。这是测试的“行为”部分,从结果中,我们检查以确保我们只取回我们已插入数据库的用户。在测试结束时,数据库被删除。当 SQL 数据库中的列不存在时,我们在这里发现了错误

某些功能需要我们与 Oracle 数据库交互。例如,如果将一条新记录添加到 Oracle,然后触发器/db 过程将启动并将该记录传输到 SQL,然后我们需要将其提升到各个层。在本例中,我们有一个 OracleSQL 集成测试基类。正如您可能已经猜到的那样,这遵循类似的模式,但创建 Oracle 和 SQL dbs 将测试数据插入 Oracle 并在测试结束时将它们都吹走。

开发人员通常选择 Web 服务层来编写他们的集成测试。另一方面,测试人员使用 UI 自动化工具来确保数据实际显示在屏幕上。例如,他们将记录一个进入网页的测试,单击搜索按钮,将“60”放入年龄框,单击搜索按钮等。该测试可能会利用相同的测试数据 sql 脚本来插入开发人员编写的测试数据(或者测试团队可能会来找开发人员并请求帮助制作 sql 脚本以插入他们能想到的任何高度复杂的数据)。但关键是,一旦创建了测试数据插入脚本,它就会利用相同的底层系统来炸毁整个数据库,创建一个新数据库,插入测试数据,然后运行指定的测试。

因此,要回答您的问题,您将需要两种类型的测试,单元测试和集成测试。您可能需要进行一些初始工作来创建一些基类或辅助方法来创建/删除数据库,自动部署以安装/卸载系统的其他组件等。无论如何,您都必须为最终部署执行此操作。集成测试也将与您的部署策略密切相关并依赖于您的部署策略。在我看来,这是一个优势而不是劣势。虽然一开始设置它可能会很痛苦,但您的集成测试隐式测试的一件事是您的部署机制。如果在部署/安装系统所需的任何组件时遇到任何问题,您希望尽快了解它。

一套好的测试是无价的。它还需要孤立、严谨和全面。测试不应该在不需要时失败,但更重要的是,它们应该在需要时失败。当它们确实失败时,您希望它们提供尽可能多的信息,并为您指出失败的确切位置。这使得解决问题变得更加容易。任何时候你投入构建这个测试套件都将很快收回成本。

于 2013-03-15T04:00:50.960 回答