清理断言抛出IntegrityError的单元测试后的状态

2 投票
1 回答
3395 浏览
提问于 2025-04-17 22:58

我有一个Django模型,里面有一个叫“title”的字段,类型是CharField(unique=True),意思是这个字段的值必须是唯一的。我写了一个单元测试,用来检查如果尝试创建第二个同样标题的实例,会抛出一个IntegrityError错误。(我在用pytest和pytest-django进行测试。)

我写的代码大概是这样的:

class Foo(models.Model):
    title = models.CharField(unique=True)

def test_title_is_unique(db):
    Foo.objects.create(title='foo')
    with pytest.raises(IntegrityError):
        Foo.objects.create(title='foo')

这段代码运行得很好,但问题是上面的代码没有包含清理的部分。pytest-django不会自动清理数据库,所以在你创建或保存模型实例时,需要自己注册清理处理程序。可以这样做:

def test_title_is_unique(request, db):
    foo = Foo.objects.create(title='foo')
    request.addfinalizer(foo.delete)
    with pytest.raises(IntegrityError):
        Foo.objects.create(title='foo')

好的,这样可以。但如果第二个.create()调用错误地成功了呢?我还是想清理掉那个实例,但只有在它(错误地)被创建的时候才需要清理。

这是我最后决定的做法:

def test_title_is_unique(request, db):
    foo = Foo.objects.create(title='foo')
    request.addfinalizer(foo.delete)
    try:
        with pytest.raises(IntegrityError):
            new_foo = Foo.objects.create(title='foo')
    finally:
        if 'new_foo' in locals():
            request.addfinalizer(new_foo.delete)

不过,这样做感觉不是特别优雅,也不太符合Python的风格,更不用说有很多行代码其实根本不应该运行。

我该怎么做才能确保如果第二个模型实例被创建了就能清理掉,同时又能减少一些繁琐的步骤,或者用更少的代码行呢?

1 个回答

2

你不需要担心清理工作。pytest-django的数据库工具会在测试时关闭事务,这样整个测试就会在一个事务中执行,最后会把这个事务回滚。这样可以确保数据库保持干净。

如果测试需要使用事务,可以使用transactional_db工具,这样会开启事务(速度会慢一些),并在测试结束后清空整个数据库的内容(会非常慢),同样是帮你清理。

所以如果清理没有发生,那你可能需要在pytest-django上报告一个bug。不过我会很惊讶如果真是这样,除非我漏掉了什么重要的东西。

撰写回答