我有一个相当复杂的 Django(Rest Framework) View ,它更新数据库中的对象。
为了更新对象,需要满足一些条件(这些不是真实条件,但它们是相似的):
- 用户必须登录
- 用户名需要以
admin开头 - 根据一些规则,更新数据需要有效
- 一个单独的昂贵函数
is_moon_phase_ok需要返回True
我正在尝试为此 View 编写一组可靠的单元测试,我想出的场景如下:
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 行代码。
想象一下,如果需求发生变化,而且它们确实发生了变化,那么忽略测试用例、确保它们仍然适用、根据新需求更新它们等会是多么痛苦。
有没有更好的方法来完成相同的事情,具有相同的测试覆盖率,但工作量更少?
请您参考如下方法:
对我来说,这听起来很像参数化测试,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
你也可以有多层参数,eg.:
@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
