问题描述
|
我正在从事的项目是包装为Python软件包的业务逻辑软件。这个想法是,各种脚本或应用程序将导入它,对其进行初始化,然后再使用它。
当前,它具有用于初始化并设置各种内容的顶级init()方法,一个很好的示例是它使用db连接设置sqlAlchemy并存储SA会话以供以后访问。它被存储在我的项目的子包中(即myproj.model.Session,因此其他代码在导入模型后可以得到有效的SA会话)。
长话短说,这使我的包裹成为有状态的包裹。我正在为该项目编写单元测试,这种稳定的行为带来了一些问题:
测试应该隔离,但是我程序包的内部状态打破了隔离
我无法测试主要的init()方法,因为其行为取决于状态
未来的测试将需要针对具有已知模型状态(例如,预先填充的sqlite内存数据库)的(尚未编写)控制器部分运行
我应该以某种方式重构我的软件包,因为当前结构不是最佳(可能)实践(tm)? :)
我应该把它留在那儿,每次设置/拆卸整个东西吗?如果我要实现完全隔离,那就意味着在每个测试中都将完全擦除并重新填充数据库,这不是很过分吗?
这个问题确实在整个代码和测试结构上,但是对于我来说,使用鼻子1.0进行测试有什么价值?我知道Isolate插件可能会对我有所帮助,但是我想在测试套件中做一些奇怪的事情之前先弄清楚代码。
解决方法
您有几种选择:
模拟数据库
有一些权衡需要注意。
由于必须进行连接的设置,拆除和模拟,因此测试将变得更加复杂。您可能还想对发送的SQL /命令进行验证。它还可能会产生奇怪的紧密耦合,这可能会导致您在架构或SQL更改时花费额外的时间来维护/更新测试。
这通常是测试隔离的最纯粹方法,因为它减少了潜在的测试依赖性。在连续集成环境中,它还可以使测试更快,并减少自动化测试套件的开销。
用每个测试重新创建数据库
权衡要注意。
根据重新创建数据库实际花费的时间,这会使测试非常缓慢。如果开发数据库服务器是共享资源,则必须进行额外的初始投资以确保每个开发服务器上都有自己的数据库。服务器可能会受到影响,具体取决于测试运行的频率。在连续集成环境中运行测试套件会有额外的开销,因为它将至少需要(可能需要更多)数据库(取决于同时构建的分支数量)。
好处与实际运行将在生产中使用的相同代码路径和相似资源有关。这通常有助于尽早发现错误,这总是一件好事。
ORM数据库交换
如果您使用像SQLAlchemy这样的ORM,则有可能将基础数据库与可能更快的内存数据库交换。这使您可以减轻上述两个选项的负面影响。
它与生产中使用的数据库并不完全相同,但是ORM应该有助于减轻掩盖错误的风险。通常,建立内存数据库的时间比文件备份的时间短得多。它还具有隔离到当前测试运行的好处,因此您不必担心共享资源管理或最终拆卸/清除。
,在具有相对昂贵的设置(IPython)的项目上工作,我已经看到了一种方法,我们称之为
get_ipython
函数,该函数设置并返回一个实例,同时用返回对现有实例的引用的函数替换自身。然后,每个测试都可以调用相同的函数,但是仅对第一个函数进行设置。
这样可以省去为每个测试执行冗长的设置过程的时间,但有时会产生奇怪的情况,即测试失败或通过,具体取决于之前运行的测试。我们有解决这种问题的方法-无论状态如何,很多测试都应该做同样的事情,并且我们可以尝试在某些测试之前重置对象的状态。您可能会发现类似的折衷方案。
,Mock是实现某些隔离的简单而强大的工具。 Pycon2011提供了一个不错的视频,展示了如何使用它。我建议将其与py.test一起使用,这样可以减少定义测试所需的代码量,并且功能仍然非常非常强大。