使用 Django 进行测试驱动开发

Test Driven Development with Django

我有一个关于使用 Django 进行测试驱动开发的概念性问题,也可能适用于其他框架。

TDD 指出开发周期的第一步是编写失败的测试。

假设对于一个单元测试,我想验证在请求到达时是否实际创建了一个项目。为了测试此功能,我想向测试客户端发出请求,并检查数据库是否实际创建了此对象。为此,我需要在测试文件中导入相关模型,但由于第一步是编写此测试,我什至还没有模型。所以我无法 运行 测试失败。

此处建议的方法是什么?也许先写一个更简单的测试,然后在实现足够级别的生产代码后修改测试?

在 Django 中,方法总是重新创建用于测试和暂存的工作环境。在测试中数据是假的,在暂存中数据是 "old" 或与生产非常相似。

重要说明:您描述的不是单元测试。它不测试一个单元。它测试了从 django url 布线、视图开始到模型结束的一大堆东西。这是集成测试。其次,不要创建使用外部 API(或大部分相同的测试客户端)来创建数据的测试,而是通过直接转到数据库来检查实体是否已创建。这个不好。如果你通过一些 API 创建数据,你应该使用相同级别的 API 来检查数据是否创建。所以我的解释会讲到这个方法。

您描述的是开始使用 TDD 时的常见问题。

关于 TDD 的重要事项是您:

  1. 小步走
  2. 测试后重构是绿色的(包括测试重构)

这听起来可能很简单,您很可能已经阅读并知道这一点,但对您的工作结构的影响可能并不那么明显。

主要后果是您没有在实现功能之前从头开始编写完整的测试。你从你能做的最简单的测试开始,让它工作(通过实现一些功能),重构。然后你通过添加更多你想检查的东西来改变测试,实现那部分使测试绿色,重构等等。

这意味着您需要拆分工作(或计划如何通过简单的步骤实现它)才能在此模式下工作。这需要一些实践,我想这是采用 TDD 的主要障碍之一。

它与您写的内容相似(但有重要区别):

Maybe write a simpler test first, then modify the test after enough level of production code is implemented?

您需要先进行简单测试,然后通过小步骤迭代修改它,但是之前您实现生产代码而不是之后。

在这种特殊情况下,您可以按照以下步骤实施:

1 创建使用测试客户端的测试

def test_entity_creation(self):
    post_result = test_client.post(POST_URL, {})

    get_result = test_client.get(get_entity_url_from(post_result))

    assert_that(get_result, not_none())

您有一个测试失败但没有编写任何代码行。 请注意,尚未传递任何数据,并且检查非常基本。

2 创建url 布线和空视图

这样做才能使测试通过。您只需对代码进行很少的更改,并且视图不会 return 太多(如果有的话)。此时视图可以 return 一些硬编码 json/dict。

3.1 检查实体id是否生成

def test_entity_creation(self):
    post_result = test_client.post(POST_URL, {})

    get_result = test_client.get(get_entity_url_from(post_result))

    assert_that(get_result, not_none())
    assert_that(get_result, has_field('id', not_none()))

您可以通过将 id 添加到硬编码的 dict 来使此测试工作。

3.1 检查实体的唯一id是否生成

添加检查 ID 是否唯一的新测试:

def test_create_generates_unique_id(self):
    post_result1 = test_client.post(POST_URL, {})
    post_result2 = test_client.post(POST_URL, {})

    assert_that(get_id(post_result1), not_(equal_to(get_id(post_result2)))

4 添加只有id的模型

添加一个只有 id 的模型并从视图中添加它的创建和检索并不难。不要添加您需要的所有字段,稍后您将逐步添加。

5 加一个字段给你测试

def test_entity_creation(self):
    post_result = test_client.post(POST_URL, {'field': 'value'})

    get_result = test_client.get(get_entity_url_from(post_result))

    assert_that(get_result, not_none())
    assert_that(get_result, has_field('field', 'value'))

向模型添加一个字段并使测试通过。

6 继续做 TDD

添加更多测试和生产代码。

更多想法

对于一个 TDD 周期来说,第 4 步可能太大了。它至少需要改变三件事:

  1. post 查看处理程序
  2. 获取视图处理程序
  3. 型号

在许多情况下,通过首先为模型本身创建测试来拆分它是有意义的。不适用于测试客户端但看起来像这样的测试:

def test_entity(self):
    entity = Entity.objects.create()

    entity = Entity.objects.get(entity.id)

    assert_that(entity.id, not_none())

然后你添加一个模型。确保 test_entity 通过,并且只有在修改视图之后才能使用您的(已经测试过的)模型。

我希望这能给出解决这个问题的想法。