提问人:Reina297 提问时间:5/25/2023 更新时间:5/25/2023 访问量:121
Pytest 单元测试出现无法实例化抽象类错误
Pytest unit testing getting a Can't instantiate abstract class error
问:
我目前正在使用 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)
运行测试时,我收到“无法实例化抽象类”错误。如果有人可以帮助解决这个错误,将不胜感激。谢谢!
答:
1赞
someone
5/25/2023
#1
创建抽象类的意义在于,它是一个永远不会实例化的类。相反,你必须实现它的一个非抽象子类,它实现了两个抽象方法,然后这就是你实例化的内容。
当你为抽象类和抽象类本身编写单元测试时,你需要定义一个非抽象测试子类,如下所示:Storm_Centre
Storm
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 中定义。Storm
storm.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
我半信半疑:任何猴子修补的东西,虽然是暂时的,但如果你做得正确,都是全球性的。这就是猴子修补它的原因:仅在测试期间(理想情况下),全局更改某些内容。在这种情况下,我们在测试期间全局使非抽象化。但是你的观点是,作为上下文管理器,我们可以使更改尽可能短暂,从而不会产生意想不到的后果,并最大限度地减少在某个地方隐藏错误的机会。Storm
unittest.mock.patch
0赞
chepner
5/25/2023
是的,我在想你孤立地处理一个实例的情况,而不是在一个更大的上下文中。如果你无法控制被测代码的使用方式,那么显式的具体子类会更好。(我确实在答案中省略了您可能也会使用类似的东西来暂时删除列表。Storm
Storm
unittest.mock.patch
0赞
someone
5/25/2023
是的,我可能会很想这样做:但仅此而已。我只在真的需要时才使用猴子补丁。with patch('Storm.Storm.__abstractmethods__', frozenset()): s = Storm('Jeff', 10)
0赞
chepner
5/25/2023
对不起,是的,值得一投赞成票。我不知道为什么我没有早点这样做。
评论