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

Posted

技术标签:

【中文标题】在 Django 的基于类的视图中模拟函数【英文标题】:Mocking functions in Django's class based views 【发布时间】:2013-03-17 22:36:19 【问题描述】:

我正在将 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 之间共享。但是,我在为我的单元测试模拟它时遇到了问题。这是因为类方法 as_view 创建了一个新的实例和类调度,并返回调度返回的“处理程序”函数。因此,我似乎无法在基于类的视图中获取共享方法来模拟它。

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

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

【问题讨论】:

【参考方案1】:

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

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

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

您也可以通过将视图视为轻量抽象来促进这一点,该抽象处理诸如将参数编组 (GET/POST) 等处理到您的其余代码并调用封装在其他地方的必要逻辑。 IMO,如果您想要可测试的代码,那么 99% 的逻辑应该在 Web 上下文之外,除非它对流程绝对至关重要。这也使得在后台和并行运行事情变得更容易。

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

我意识到使用基于类的视图的一件事是它们非常适合与 mixins 一起使用并强制执行一些约定(返回 json、打开图形属性等),但使用更“高级”的基于类的视图直接需要模型,例如因为 DetailView 只会让事情变得不必要地复杂化。这些视图非常适合管理屏幕,但对于真正的应用程序来说,帮助大于伤害。除非您找到一种很好的、​​无缝的方式来集成缓存层等,否则它们会使事情变得难以测试和破坏性能。这个时候,一般只是继承自View或者TemplateView就可以了。

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

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

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

;tldr

    将类视图之外的逻辑放入适当的对象和模块中。

    不要把自己锁在试图让各种捆绑的基于类的视图适用于实际应用程序和每个用例;自己动手。

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

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

【讨论】:

以上是关于在 Django 的基于类的视图中模拟函数的主要内容,如果未能解决你的问题,请参考以下文章

如何在 django 中使用基于类的视图

Django中基于类的视图上带有参数的函数装饰器

django-select2 基于类或基于函数的视图

Django编写RESTful API:基于类的视图

django-基于类的视图

按函数名称的 Django 反向基于类的视图不起作用