为什么测试 class 方法首先会导致其他应该引发错误的单元测试失败?

Why does testing a class method first cause other unit tests that should raise an error to fail?

我正在 运行从命令行 python -m unittest 进行一些单元测试 python -m unittest

由于我的单元测试的命名,测试 class 方法的单元测试首先是 运行。

这将导致其他测试无缘无故地失败。失败的测试都是为了引发错误,但它们都失败了,声称错误从未引发过。在 IDE 中调试这些测试时,会正确引发错误。此外,当这些测试单独 运行 时,它们会通过。

当我从测试中完全删除测试 class 方法的函数时。所有测试都在命令行上顺利通过。

任何人都可以解释为什么会发生这种情况以及我可以做些什么来解决这个问题吗?下面提供了一个 MWE 和测试输出:

代码

import unittest

class MyClass:
    def __init__(self):
        self._my_val = None

    @classmethod
    def from_dict(cls, dict_):
        cls.my_val = dict_['my_val']

    @property
    def my_val(self):
        return self._my_val

    @my_val.setter
    def my_val(self, value):
        if value == 5:
            raise ValueError
        self._my_val = value


class Tests(unittest.TestCase):
    def test_classmethod(self):
        MyClass.from_dict({
            'my_val': 1
        })

    def test_my_val_raise_error(self):
        m = MyClass()
        with self.assertRaises(ValueError):
            m.my_val = 5
            
    def test_my_val_no_error(self):
        m = MyClass()
        m.my_val = 4
        self.assertEqual(m.my_val, 4)


if __name__ == '__main__':
    unittest.main()

输出

"C:\Program Files\Python39\python.exe" C:/Users/user/AppData/Roaming/JetBrains/PyCharmCE2020.3/scratches/scratch.py
..F
======================================================================
FAIL: test_my_val_raise_error (__main__.Tests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\Users\user\AppData\Roaming\JetBrains\PyCharmCE2020.3\scratches\scratch.py", line 31, in test_my_val_raise_error
    m.my_val = 5
AssertionError: ValueError not raised

----------------------------------------------------------------------
Ran 3 tests in 0.001s

FAILED (failures=1)

Process finished with exit code 1

这里发生的是您的 class 有一个名为 my_val 的 属性。 但是您的 from_dict class 方法 覆盖了 它并且 my_val 变成了普通属性(不再是 属性)。

下面的代码对此进行了演示。

属性可能很棘手,欢迎阅读我就此主题撰写的博客 here

class MyClass:
    def __init__(self):
        self._my_val = None

    @classmethod
    def from_dict(cls, dict_):
        cls.my_val = dict_['my_val']

    @property
    def my_val(self):
        return self._my_val

    @my_val.setter
    def my_val(self, value):
        if value == 5:
            raise ValueError
        self._my_val = value
        

# before override
print(MyClass.my_val) # --> <property object at 0x7f9725423950>

# override
MyClass.from_dict({'my_val': 1})

# after override
print(MyClass.my_val) # --> 1
print(type(MyClass.my_val))  # --> <class 'int'>