为啥 pytest 在测试模型创建时会抛出“AttributeError:'NoneType'对象没有属性'_meta'”错误?

Posted

技术标签:

【中文标题】为啥 pytest 在测试模型创建时会抛出“AttributeError:\'NoneType\'对象没有属性\'_meta\'”错误?【英文标题】:Why is pytest throwing an "AttributeError: 'NoneType' object has no attribute '_meta'" error when testing model creation?为什么 pytest 在测试模型创建时会抛出“AttributeError:'NoneType'对象没有属性'_meta'”错误? 【发布时间】:2020-08-11 09:05:49 【问题描述】:

我正在使用 Django 2 并尝试为我的模型编写一些单元测试。我有这些模型...

class CoopTypeManager(models.Manager):

    def get_by_natural_key(self, name):
        return self.get_or_create(name=name)[0]


class CoopType(models.Model):
    name = models.CharField(max_length=200, null=False, unique=True)

    objects = CoopTypeManager()


class CoopManager(models.Manager):
    # Look up by coop type
    def get_by_type(self, type):
        qset = Coop.objects.filter(type__name=type,
                                   enabled=True)
        return qset

class Coop(models.Model):
    objects = CoopManager()
    name = models.CharField(max_length=250, null=False)
    types = models.ManyToManyField(CoopType)
    address = AddressField(on_delete=models.CASCADE)
    enabled = models.BooleanField(default=True, null=False)
    phone = PhoneNumberField(null=True)
    email = models.EmailField(null=True)
    web_site = models.TextField()

我创建了以下工厂来自动生成这些模型...

import factory
from .models import CoopType, Coop


class CoopTypeFactory(factory.DjangoModelFactory):
    """
        Define Coop Type Factory
    """
    class Meta:
        model = CoopType


class CoopFactory(factory.DjangoModelFactory):
    """
        Define Coop Factory
    """
    class Meta:
        model = Coop

    coop_type = factory.SubFactory(CoopTypeFactory)

然后我创建了这个简单的测试......

import pytest
from django.test import TestCase
from .tests.factories import CoopTypeFactory, CoopFactory


class ModelTests(TestCase):
    @classmethod
    def setUpTestData(cls):
        print("setUpTestData: Run once to set up non-modified data for all class methods.")
        pass

    def setUp(self):
        print("setUp: Run once for every test method to setup clean data.")
        pass

    @pytest.mark.django_db
    def test_coop_type_model():
        """ Test coop type model """
        # create coop type model instance
        coop_type = CoopTypeFactory(name="Test Coop Type Name")
        assert coop_type.name == "Test Coop Type Name"

但是当我运行测试时,我得到一个错误,“AttributeError: 'NoneType' object has no attribute '_meta'”

(venv) localhost:web davea$ python manage.py test
Creating test database for alias 'default'...
Got an error creating the test database: (1007, "Can't create database 'test_maps_data'; database exists")
Type 'yes' if you would like to try deleting the test database 'test_maps_data', or 'no' to cancel: yes
Destroying old test database for alias 'default'...
Traceback (most recent call last):
  File "manage.py", line 21, in <module>
    main()
  File "manage.py", line 17, in main
    execute_from_command_line(sys.argv)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.7/site-packages/django/core/management/__init__.py", line 371, in execute_from_command_line
    utility.execute()
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.7/site-packages/django/core/management/__init__.py", line 365, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.7/site-packages/django/core/management/commands/test.py", line 26, in run_from_argv
    super().run_from_argv(argv)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.7/site-packages/django/core/management/base.py", line 288, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.7/site-packages/django/core/management/base.py", line 335, in execute
    output = self.handle(*args, **options)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.7/site-packages/django/core/management/commands/test.py", line 59, in handle
    failures = test_runner.run_tests(test_labels)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.7/site-packages/django/test/runner.py", line 601, in run_tests
    old_config = self.setup_databases()
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.7/site-packages/django/test/runner.py", line 548, in setup_databases
    self.parallel, **kwargs
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.7/site-packages/django/test/utils.py", line 176, in setup_databases
    serialize=connection.settings_dict.get('TEST', ).get('SERIALIZE', True),
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.7/site-packages/django/db/backends/base/creation.py", line 76, in create_test_db
    self.connection._test_serialized_contents = self.serialize_db_to_string()
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.7/site-packages/django/db/backends/base/creation.py", line 119, in serialize_db_to_string
    serializers.serialize("json", get_objects(), indent=None, stream=out)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.7/site-packages/django/core/serializers/__init__.py", line 128, in serialize
    s.serialize(queryset, **options)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.7/site-packages/django/core/serializers/base.py", line 80, in serialize
    for count, obj in enumerate(queryset, start=1):
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.7/site-packages/django/db/backends/base/creation.py", line 116, in get_objects
    yield from queryset.iterator()
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.7/site-packages/django/db/models/query.py", line 336, in _iterator
    yield from self._iterable_class(self, chunked_fetch=use_chunked_fetch, chunk_size=chunk_size)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.7/site-packages/django/db/models/query.py", line 54, in __iter__
    results = compiler.execute_sql(chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.7/site-packages/django/db/models/sql/compiler.py", line 1050, in execute_sql
    sql, params = self.as_sql()
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.7/site-packages/django/db/models/sql/compiler.py", line 445, in as_sql
    extra_select, order_by, group_by = self.pre_sql_setup()
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.7/site-packages/django/db/models/sql/compiler.py", line 50, in pre_sql_setup
    self.setup_query()
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.7/site-packages/django/db/models/sql/compiler.py", line 40, in setup_query
    self.query.get_initial_alias()
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.7/site-packages/django/db/models/sql/query.py", line 886, in get_initial_alias
    alias = self.join(BaseTable(self.get_meta().db_table, None))
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.7/site-packages/django/db/models/sql/query.py", line 284, in get_meta
    return self.model._meta
AttributeError: 'NoneType' object has no attribute '_meta'

编辑:如果您想尝试重现问题,这里是一个 github 链接。项目在“网络”——https://github.com/chicommons/maps

【问题讨论】:

我试图重现该问题,但测试对我来说是无缝运行的 (screenshot)。我认为这个问题可能特定于您的环境。 您的问题可能来自迁移,您确定它们是最新的吗? @SebCorbin ,当我运行“python manage.py makemigrations”时,我得到“未检测到更改”。当我运行“python manage.py migrate maps”时,我得到“No migrations to apply”。 @ArakkalAbu,我将 GitHub 项目链接作为对我问题的编辑。如果您有时间,我很想看看您是否能够重现该问题。 @Dave 尝试在新的数据库上运行您的迁移,如果您在执行此操作时遇到错误,则可以缩小问题范围 【参考方案1】:

简单的解决方案是,在测试运行期间忽略迁移。为此,请为测试环境创建一个单独的设置模块

# /web/maps/test_settings.py
from .settings import *


class DisableMigrations(object):
    # ref: https://***.com/a/28560805/12578202

    def __contains__(self, item):
        return True

    def __getitem__(self, item):
        return None


MIGRATION_MODULES = DisableMigrations()

然后运行测试,

python manage.py test <b>--settings=maps.test_settings</b>
Screenshot

注意:我已经对存储库进行了其他小的更改以完成它,但我希望这些事情不是什么大问题。 (反正我给你做了a PR here)

【讨论】:

嗨@ArakkalAbu,您介意分享您所做的其他小改动吗?如果你想分叉我的 repo 并在那里添加你的更改,我可以直接查看它们。我认为您的解决方案正在运行,并且我遇到的错误(“ModuleNotFoundError: No module named 'tests.factories'”)很容易解决,但我想验证一下。 你没看过我的PR吗? ? 我已经在我的答案中附加了链接。 (我做了一些其他的清理,检查我的提交信息......我希望这是不言自明的??)@Dave 天哪,我完全错过了(我经常忽略我的 GitHub 电子邮件)!无论如何下载了您的 PR,并且运行良好。一个问题——忽略迁移有什么副作用吗?没有它们,工厂机器人是否无法做或不知道哪些事情? 据我所知没有问题。 :)【参考方案2】:

好的,我在 github 上查看了您的代码,我的第一个警告是:如果可以,请不要对其他包的模型、子类进行猴子补丁

您对 django-address 的使用有点乱,您正在使用模型并修改它们的行为,但这很危险。

罪名是

setattr(State._meta, 'default_manager', StateCustomManager())
setattr(Locality._meta, 'default_manager', LocalityCustomManager())

这些属性是私有的(因此 __meta 为前缀,不应混淆。我会改用 State.add_to_class('objects', StateCustomManager())

第二个警告是:我真的不认为您使用 get_by_natural_key() 并自动创建对象是非常明智的,它可能会减慢您的对象访问速度。

【讨论】:

谢谢,尽管我认为有人向我建议的原因是因为这是加载我的种子数据的唯一方法——“/maps/fixtures/seed_data.yaml”。如果我尝试您的解决方案,我将无法再运行我的种子数据命令(使用“python manage.py loaddata maps/fixtures/seed_data.yaml”运行) 您是否使用manage.py dumpdata 创建了夹具?如果是这样,请务必输入选项--natural-foreign 和/或--natural-primary 嗨@SebCorbin,没有使用脚本(不是转储数据)创建夹具文件。请注意,我并不喜欢使用 pytest 或工厂方法来运行我的简单测试——我只是认为这是创建测试的最简单方法,但我愿意接受另一种完成测试的方法。 那我建议docs.djangoproject.com/en/3.0/topics/testing 不过可能解决不了你的问题,fixture 应该用 django 创建才能完全合规,或者如果是关于导入数据,你可以创建一个命令docs.djangoproject.com/en/3.0/howto/custom-management-commands 【参考方案3】:

您没有在任何一个上调用 Supper 类构造函数 你的派生类。编写派生类时,一定要调用 子类的超类构造函数:

class SubClass(SupperClass):
    def __init__(self, *args, **kwargs):
        super(SubClass, self).__init__(*args, **kwargs)

【讨论】:

以上是关于为啥 pytest 在测试模型创建时会抛出“AttributeError:'NoneType'对象没有属性'_meta'”错误?的主要内容,如果未能解决你的问题,请参考以下文章

为啥Visual Studio在声明字符串数组列表时会抛出异常

为啥 Dash 在通过 PyCharm 调试时会抛出 TypeError? [关闭]

为啥 InputStreamReader 从 jar 读取时会抛出 NPE?

为啥我的自定义 UICollectionViewLayout 在单元格之间添加空格时会抛出错误?

为啥我的 Tomcat 服务器在编译 JSP 时会抛出间歇性 404?

为啥 GuzzleHttp 客户端在 Laravel/Lumen 上使用它发出网络请求时会抛出 ClientException?