【问题标题】:How to avoid combinatorial explosion in unit testing Django views如何避免单元测试 Django 视图中的组合爆炸
【发布时间】:2019-03-09 12:42:24
【问题描述】:

我有一个相当复杂的 Django (Rest Framework) 视图,它更新数据库中的一个对象。

为了更新对象,需要满足一些条件(这些不是真实条件,但它们是相似的):

  1. 用户必须登录
  2. 用户名需要以admin开头
  3. 更新数据需要根据一些规则有效
  4. 一个单独的昂贵函数is_moon_phase_ok需要返回True

我正在尝试为此视图编写一组可靠的单元测试,我想出的场景如下:

when not logged in, return 401
when not logged in, return {"fail": "login"}
when not logged in, don't touch database
when not logged in, don't check moon phase
when username is not admin_*, return 401
when username is not admin_*, return {"fail": "username"}
when username is not admin_*, don't touch database
when username is not admin_*, don't check moon phase
when invalid data, return 400
when invalid data, return {"fail": "data"}
when invalid data, don't touch database
when invalid data, don't check moon phase
when logged in, return 200
when logged in, return updated data
when logged in, check moon phase
when logged in, update database

如您所知,这些单元测试中的每一个都需要相当多的代码来设置和执行。就我而言,每个单元测试需要 7 到 20 行代码。

想象一下,如果需求发生了变化,而且它们确实发生了变化,那么忽略测试用例、确保它们仍然适用、根据新需求更新它们等等会是多么痛苦。

有没有更好的方法来完成同样的事情,具有相同的测试覆盖率,但劳动力更少?

【问题讨论】:

  • 您可以在同一个单元中进行多个“测试”:您因此制作了四种场景:未登录,用户名不是管理员,无效数据,登录,并且在每个测试中,您测试状态代码、返回内容、数据库是否被触发等
  • @WillemVanOnsem:这就是我一直试图避免的,所以每个单元测试都测试 one 东西,这一切都根据人们一直告诉我的一切职业。这不适用于 Django 视图吗?
  • 你测试一件事:一个场景。单元测试(或集成测试)在一个“场景”正在展开的意义上测试一件事。但是你当然可以先编写一个“帮助”函数来设置场景,然后进行单独的测试,每个测试一个属性

标签: python django unit-testing django-rest-framework


【解决方案1】:

对我来说这听起来很像参数化测试,pytest 对此有支持,基本上你可以编写一个测试并提供输入参数,正如预期的那样。 所以你只写了一个测试但足够通用以支持不同的参数,因此需要维护的代码更少。在幕后 pytest 使用您定义的参数一一运行相同的测试。 编写通用测试可能会引入一些逻辑(如您在我的示例中所见),但我认为您可以接受这些

作为一个通用示例:

@pytest.mark.parametrize('is_admin,expected_status_code,expected_error', [
    (True, 200, {}),
    (False, 401, {"fail": "login"})
])
def test_sample(is_admin, expected_status_code, expected_data):
    # do your setup
    if is_admin:
        user =  create_super_user()
    else:
        user =  normal_user()

    # do your request
    response = client.get('something')

    # make assertion on response
    assert response.status_code == expected_status_code
    assert response.data == expected_data

你也可以有多层参数,例如:

@pytest.mark.parametrize('is_admin', [
    True,
    False
])
@pytest.mark.parametrize('some_condition,expected_status_code,expected_error', [
    (True, 200, {}),
    (False, 401, {"fail": "login"})
])

这将为 is_admin (True/False) 和其他参数的每个组合执行测试,不错吧?

在此处查看文档pytest parametrize tests

如果你不使用 pytest,请检查这个库,它做类似的事情 Parameterized testing with any Python test framework

【讨论】:

  • 我使用普通的 ol' unittest 但我可能可以制作出这样的作品。谢谢!
猜你喜欢
  • 1970-01-01
  • 2021-09-19
  • 2012-01-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-01-18
相关资源
最近更新 更多