8

我正在从事的项目是一个包装成 Python 包的业务逻辑软件。这个想法是各种脚本或应用程序将导入它,初始化它,然后使用它。

它目前有一个顶级的 init() 方法来进行初始化并设置各种东西,一个很好的例子是它使用数据库连接设置SQLAlchemy并存储 SA 会话以供以后访问。它被存储在我的项目的子包中(即 myproj.model.Session,因此其他代码可以在导入模型后获得有效的 SA 会话)。

长话短说,这使我的包成为一个有状态的包。我正在为该项目编写单元测试,这种 stafeful 行为带来了一些问题:

  1. 测试应该是隔离的,但是我的包的内部状态打破了这种隔离
  2. 我无法测试主要的 init() 方法,因为它的行为取决于状态
  3. 未来的测试将需要针对具有众所周知的模型状态的(尚未编写的)控制器部分运行(例如,预填充的sqlite in-memory db

我应该以某种方式重构我的包,因为当前结构不是最佳(可能)实践(tm)吗?:)

我应该把它留在那里并每次都设置/拆除整个事情吗?如果我要实现完全隔离,这意味着在每次测试中完全擦除并重新填充数据库,这不是矫枉过正吗?

这个问题实际上是关于整体代码和测试结构的,但是为了我的测试,我使用nose-1.0是值得的。我知道Isolate 插件可能对我有帮助,但我想在测试套件中做奇怪的事情之前得到代码。

4

3 回答 3

3

你有几个选择:

模拟数据库

有一些权衡需要注意。

您的测试将变得更加复杂,因为您必须对连接进行设置、拆卸和模拟。您可能还想验证发送的 SQL/命令。它还倾向于创建一种奇怪的紧密耦合,这可能会导致您在架构或 SQL 更改时花费额外的时间来维护/更新测试。

这通常是最纯粹的测试隔离,因为它减少了来自测试的潜在大依赖。它还倾向于使测试更快,并减少在持续集成环境中自动化测试套件的开销。

使用每个测试重新创建数据库

要注意的权衡。

这可能会使您的测试非常缓慢,具体取决于重新创建数据库实际需要多少时间。如果开发数据库服务器是共享资源,则必须进行额外的初始投资以确保每个开发人员在服务器上都有自己的数据库。服务器可能会受到影响,具体取决于测试运行的频率。在持续集成环境中运行测试套件会产生额外的开销,因为它至少需要,可能需要更多的数据库(取决于同时构建的分支数量)。

好处与实际运行将在生产中使用的相同代码路径和类似资源有关。这通常有助于更早地发现错误,这总是一件好事。

ORM 数据库交换

如果您使用像 SQLAlchemy 这样的 ORM,那么您可以将底层数据库与可能更快的内存数据库交换。这使您可以减轻前两个选项的一些负面影响。

它与生产环境中使用的数据库并不完全相同,但 ORM 应该有助于降低掩盖错误的风险。通常,设置内存数据库的时间比文件支持的要短得多。它还具有与当前测试运行隔离的好处,因此您不必担心共享资源管理或最终拆卸/清理。

于 2011-04-17T05:09:21.070 回答
1

在一个设置相对昂贵的项目(IPython)上工作时,我看到了一种方法,我们调用一个get_ipython函数,该函数设置并返回一个实例,同时用一个返回对现有实例的引用的函数替换自身。然后每个测试都可以调用相同的函数,但它只对第一个进行设置。

这节省了为每个测试执行冗长的设置过程,但有时它会产生奇怪的情况,即测试失败或通过,具体取决于之前运行的测试。我们有办法解决这个问题——不管状态如何,很多测试都应该做同样的事情,我们可以尝试在某些测试之前重置对象的状态。您可能会发现类似的折衷方案适合您。

于 2011-04-15T11:53:51.303 回答
0

Mock是一个简单而强大的工具来实现一些隔离。Pycon2011有一个很好的视频,它展示了如何使用它。我建议将它与 py.test 一起使用,它减少了定义测试所需的代码量,并且仍然非常非常强大。

于 2011-04-15T12:53:24.130 回答