代码之家  ›  专栏  ›  技术社区  ›  zerohedge

pytest ScopeMismatch错误:如何正确使用fixture

  •  1
  • zerohedge  · 技术社区  · 6 年前

    对于以下代码:

    @pytest.fixture(scope="module")
    def dummy_article(request, db):
        return mixer.blend("core.article", title="this one price", internal_id=3000)
    
    
    def test_article_str_method(dummy_article):
        assert (
            str(dummy_article)
            == f"article with ID {dummy_article.internal_id} and title: {dummy_article.title}"
        )
    

    我得到以下错误:

    ScopeMismatch: You tried to access the 'function' scoped fixture 'db' with a 'module' scoped request object, involved factories
    core/tests/test_article_model.py:13:  def dummy_article(request, db)
    

    如果我把夹具换成 scope="function" ,但这违背了将其用于其他测试而不必为每个测试“设置”的目的。

    我怎么能有固定装置 db 范围比 function ?

    1 回复  |  直到 6 年前
        1
  •  4
  •   hoefling    6 年前

    这个 db 固定装置有 function 作用域是有原因的,因此每个测试结束时的事务回滚确保数据库保持在测试开始时的相同状态。不过,您可以使用 django_db_blocker 固定装置:

    @pytest.fixture(scope='module')
    def get_all_models(django_db_blocker):
        with django_db_blocker.unblock():
            return MyModel.objects.all()
    

    警告

    请注意,在会话范围内解锁数据库时,如果在其他fixture或测试中更改数据库,则需要自己完成。在下面的示例中,我创建了 Foo 在会话范围的fixture中 create_foo ,然后在中缓存会话的queryset all_foos :

    # models.py
    
    from django.db import models
    
    class Foo(models.Model):
        name = models.CharField(max_length=16)
    


    # test_foo.py
    
    import pytest
    from app.models import Foo
    
    @pytest.fixture(scope='session', autouse=True)
    def create_foo(django_db_blocker):
        with django_db_blocker.unblock():
            Foo.objects.create(name='bar')
    
    
    @pytest.fixture(scope='module')
    def all_foos(django_db_blocker):
        with django_db_blocker.unblock():
            yield Foo.objects.all()
    
    
    def test_1(all_foos):
        assert all_foos.exists()
    
    def test_2(all_foos, db):
        all_foos.delete()
        assert not Foo.objects.exists()
    
    def test3(all_foos):
        assert all_foos.exists()
    

    之后 test_2 运行,存储在会话中的queryset 所有人 将为空,导致 test_3 失败:

    test_foo.py::test_1 PASSED                                                           [ 33%]
    test_foo.py::test_2 PASSED                                                           [ 66%]
    test_foo.py::test_3 FAILED                                                           [100%]
    
    ========================================= FAILURES ========================================
    __________________________________________ test_3 _________________________________________
    
    all_foos = <QuerySet []>
    
        def test_3(all_foos):
    >       assert all_foos.exists()
    E       assert False
    E        +  where False = <bound method QuerySet.exists of <QuerySet []>>()
    E        +    where <bound method QuerySet.exists of <QuerySet []>> = <QuerySet []>.exists
    
    test_foo.py:28: AssertionError
    

    结果:如果不想引入可以在测试中更改的全局状态,请不要在会话范围中存储引用。从数据库查询数据并返回副本或序列化数据,等等。

    安全使用示例:

    @pytest.fixture(scope='session')
    def foo_names(django_db_blocker):
        with django_db_blocker.unblock():
            names = list(Foo.objects.values_list('name', flat=True))
        return names