在Django的基于类的视图中模拟函数

时间:2013-03-27 17:44:01

标签: django unit-testing django-models python-2.7 python-mock

我正在使用Django REST Framework来处理我正在使用的API。出于几个原因,我想使用基于类的视图。但是,我对我的单元测试有点特别,我从不允许单元测试接触数据库。注意:我总是使用Carl Meyer在Pycon 2012上演示的“技巧”,在那里他模拟了Cursor包装。

cursor_wrapper = Mock()
cursor_wrapper.side_effect = RuntimeError("No touching the database!")

@patch('django.db.backends.util.CursorWrapper', cursor_wrapper)
class TestMyCode(TestCase):

如果您对幻灯片感兴趣,请参阅link

我在其中一个视图中有一个方法来检查数据库中的某些内容。要干,它在POST和PUT之间共享。但是,我在为我的单元测试嘲笑它时遇到了问题。那是因为classmethod as_view创建了一个新的实例和类调度,并返回dispatch返回的“handler”函数。所以,我似乎无法在基于类的视图中获取共享方法来模拟它。

我可以模拟基于类的视图使用的模型,但是我必须基本上打破我的“DRY”目标,并在POST和PUT中复制代码。我想我可以重构代码并将逻辑移到模型上。但是,我不是肯定的,我想这样做。

如何模拟基于类的视图的共享方法以避免实际触及数据库?只是避免它们?

1 个答案:

答案 0 :(得分:3)

我想你回答了自己的问题。用于测试任何Web框架的相同内容适用于Django,例如控制和依赖注入的反转。你可以在Python中保持它非常简单,所以不要被像Spring这样的东西所吓倒或关闭。

为什么不将代码移出基于类的视图?如果您出于某种原因需要在其他地方使用相同的逻辑,那么您的代码仍然不会是干的。仅仅因为它的Django并不意味着良好的编程原则不适用。

我建议在新的类/ python模块中抽象出一些东西,例如服务(作为概念,而不是Django的服务定义)和其他用于数据访问的逻辑抽象。然后,您完全独立于Django视图的请求/响应生命周期。 Django和Rails开发人员倾向于将所有逻辑直接放在模型或视图中。这只会导致上帝阶级和难以测试的事物。

您可以通过将您的视图视为轻量级抽象来处理此问题,这些抽象可以处理诸如编组参数(GET / POST)之类的内容以及其他代码并调用其他地方封装的必要逻辑。 IMO,如果你想要可测试的代码,99%的逻辑应该在Web上下文之外,除非它对这个过程绝对至关重要。这使得在后台和并行运行更容易。

你应该得到的是普通的python模块和易于测试的类,因为它们没有直接依赖于HTTP。如果需要模拟HTTP,则可以简单地模拟请求对象。你很幸运,因为python / django的组合可以很容易地将这些东西作为简单的dicts / kwargs进行转储和模拟。

我使用基于类的视图实现的一件事是它们适合与mixin一起使用并强制执行一些约定(返回json,打开图形属性等),但使用更直接需要模型的更“高级”类视图因为DetailView只是不必要地复杂化了。这些视图对于管理员屏幕很有用,但对于真正的应用程序来说,这不仅仅是伤除非你找到一种很好的,无缝的方法来集成缓存层等,否则它们会使测试和谋杀性能变得困难。此时,通常只是从View或TemplateView继承并完成它。

关于数据库模拟,请创建您的模拟并完成业务逻辑。然后,只要符合一组特定的规则和接口,输入/输出的内容就无关紧要了。例如,请参阅Mixer之类的内容。您还可以在测试期间简单地创建/销毁临时DB。一种方法是为dev / staging / production / testing等创建单独的设置模块,并根据环境动态加载它们。这样,您可以避免在运行单元测试时损坏工作的dev数据库。当然,这是一种集成测试的形式,但你可能也应该做一些。上述解决方案在其他ORM中很常见,例如Hibernate。

与上一个相关,您可以在设置中执行类似下面的代码,以使用内存数据库进行单元测试。但最终,您仍需要考虑针对实际数据存储类型的集成测试,例如MySQL

if 'test' in sys.argv:
    DATABASES['default']['ENGINE'] = 'sqlite3'

; tldr

  1. 将您的逻辑放在类视图之外的适当对象和模块中。

  2. 不要试图让各种捆绑的基于类的视图适用于真实的应用程序和每个用例;滚动你自己。

  3. 通常使用良好的TDD原则,例如IOC,将所需参数传递给构造函数,松散耦合,避免过多的专有状态要求(特别是HTTP)。

    1. 通过创建标准模拟对象(参见#3)并通过类似服务的接口(参见#1)来避免数据库依赖。