Pytest 单元测试出现无法实例化抽象类错误

Pytest unit testing getting a Can't instantiate abstract class error

提问人:Reina297 提问时间:5/25/2023 更新时间:5/25/2023 访问量:121

问:

我目前正在使用 Storm_Centre.py 的 pytest 测试此代码

class Storm_Centre:
    def __init__(self):
        self.storm_list = []
        
    def already_exists(self, name) -> bool:
        for storm in self.storm_list:
            if storm.name == name:
                return True
        return False

此代码使用 Storm.py 中的代码

from abc import ABC, abstractmethod


class Storm(ABC):
    def __init__(self, name, wind_speed):
        self.name = name
        self.wind_speed = wind_speed
        super().__init__()

    @abstractmethod
    def calculate_classification(self):
        pass

    @abstractmethod
    def get_advice(self):
        pass

我正在使用以下代码在 test_storm.py 中测试代码:

import pytest
from Storm import Storm
from Storm_Centre import Storm_Centre

def test_already_exists():
    s1 = Storm('Jeff', 10)
    c1 = Storm_Centre()
    c1.storm_list.append(s1)
    assert(c1.already_exists('Jeff') == True)
    assert(c1.already_exists('Dave') == False)

运行测试时,我收到“无法实例化抽象类”错误。如果有人可以帮助解决这个错误,将不胜感激。谢谢!

python 单元测试 pytest

评论

1赞 jonrsharpe 5/25/2023
你不能实例化一个抽象类,它是抽象的

答:

1赞 someone 5/25/2023 #1

创建抽象类的意义在于,它是一个永远不会实例化的类。相反,你必须实现它的一个非抽象子类,它实现了两个抽象方法,然后这就是你实例化的内容。

当你为抽象类和抽象类本身编写单元测试时,你需要定义一个非抽象测试子类,如下所示:Storm_CentreStorm

import pytest
from storm import Storm
from storm_centre import Storm_Centre

class TestStorm(Storm):
    def calculate_classification(self):
        return 4.2
    def get_advice(self):
        return "don't panic"

def test_already_exists():
    s1 = TestStorm('Jeff', 10)
    c1 = Storm_Centre()
    c1.storm_list.append(s1)
    assert(c1.already_exists('Jeff') == True)
    assert(c1.already_exists('Dave') == False)

编辑:删除了不必要的方法。__init__

PS:你可以用@chepner的想法把它做得更小:

class TestStorm(Storm):
    pass
TestStorm.__abstractmethods__ = frozenset()

PPS:典型的 Python 文件名约定是使用小写:class 通常在 file 中定义。Stormstorm.py

1赞 chepner 5/25/2023 #2

出于测试目的,抽象基类很容易被“颠覆”。

>>> Storm.__abstractmethods__
frozenset({'calculate_classification', 'get_advice'})
>>> Storm("Jeff", 10)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class Storm with abstract methods calculate_classification, get_advice
>>> Storm.__abstractmethods__ = frozenset()
>>> Storm("Jeff", 10)
<__main__.Storm object at 0x1011d0f50>

因此,为了避免定义要实例化的具体子类的需要,您可以简单地将属性设置为空列表、set 等,实例化的尝试就会成功(因为它不再报告任何必须首先重写的方法)。__abstractmethods__

评论

0赞 someone 5/25/2023
我可以看到这会起作用,但对我来说似乎不安全:您正在以全局方式对抽象类进行猴子修补,因此,如果您有一个错误,即代码库中的一段代码试图实例化它,但您的测试套件仅在猴子修补生效时执行该段代码, 即使达到 100% 的覆盖率,您的单元测试也无法找到该错误。
0赞 someone 5/25/2023
我半信半疑:任何猴子修补的东西,虽然是暂时的,但如果你做得正确,都是全球性的。这就是猴子修补它的原因:仅在测试期间(理想情况下),全局更改某些内容。在这种情况下,我们在测试期间全局使非抽象化。但是你的观点是,作为上下文管理器,我们可以使更改尽可能短暂,从而不会产生意想不到的后果,并最大限度地减少在某个地方隐藏错误的机会。Stormunittest.mock.patch
0赞 chepner 5/25/2023
是的,我在想你孤立地处理一个实例的情况,而不是在一个更大的上下文中。如果你无法控制被测代码的使用方式,那么显式的具体子类会更好。(我确实在答案中省略了您可能也会使用类似的东西来暂时删除列表。StormStormunittest.mock.patch
0赞 someone 5/25/2023
是的,我可能会很想这样做:但仅此而已。我只在真的需要时才使用猴子补丁。with patch('Storm.Storm.__abstractmethods__', frozenset()): s = Storm('Jeff', 10)
0赞 chepner 5/25/2023
对不起,是的,值得一投赞成票。我不知道为什么我没有早点这样做。