如何使用有状态的Python模块正确实现测试隔离?

时间:2011-04-15 10:56:06

标签: python unit-testing state code-structure

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

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

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

  1. 测试应该是隔离的,但我的包的内部状态会破坏这种隔离
  2. 我无法测试主init()方法,因为它的行为取决于状态
  3. 未来的测试需要针对具有众所周知的模型状态的(尚未编写的)控制器部分运行(例如,预先填充的sqlite in-memory db
  4. 我应该以某种方式重构我的包,因为当前结构不是最佳(可能)练习(tm)? :)

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

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

3 个答案:

答案 0 :(得分:3)

您有几个选择:

模拟数据库

有一些权衡需要注意。

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

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

在每次测试时重新创建数据库

需要注意权衡。

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

这样做的好处与实际运行相同的代码路径和将在生产中使用的类似资源有关。这通常有助于揭示早期的错误,这总是一件非常好的事情。

ORM数据库交换

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

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

答案 1 :(得分:1)

使用相对昂贵的设置(IPython)处理项目时,我已经看到了一种方法,我们称之为get_ipython函数,它设置并返回一个实例,同时用一个返回的函数替换它自己对现有实例的引用。然后每个测试都可以调用相同的函数,但它只对第一个函数进行设置。

这为每次测试节省了很长的设置过程,但偶尔会出现奇怪的情况,其中测试失败或通过,具体取决于之前运行的测试。我们有办法解决这个问题 - 无论状态如何,很多测试应该做同样的事情,我们可以尝试在某些测试之前重置对象的状态。您可能会找到类似的权衡取舍。

答案 2 :(得分:0)

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