6

我正在将 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):

这是如果您对幻灯片感兴趣,链接。

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

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

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

4

1 回答 1

4

我想你回答了你自己的问题。用于测试任何 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

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

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

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

    1. 通过创建标准模拟对象(参见#3)和通过类似服务的接口(参见#1)来避免对数据库的依赖。
于 2014-03-17T08:47:10.520 回答