Почему тестирование метода класса сначала приводит к сбою других модульных тестов, которые должны вызывать ошибку?

#python-3.x #python-unittest

#python-3.x #python-unittest

Вопрос:

Я запускаю некоторые модульные тесты из командной строки python -m unittest .

Из-за именования моих модульных тестов модульный тест, который тестирует метод класса, запускается первым.

Затем это приведет к сбою других тестов без всякой причины. Все тесты, которые завершаются неудачей, должны вызывать ошибку, но все они терпят неудачу, утверждая, что ошибка никогда не возникала. Когда эти тесты отлаживаются в IDE, ошибка выдается правильно. Кроме того, когда эти тесты выполняются по отдельности, они проходят.

Когда я полностью удаляю функции, которые проверяют методы класса, из тестов. Все тесты проходят без проблем в командной строке.

Кто-нибудь может предложить объяснение, почему это происходит, и что я могу сделать, чтобы это исправить? Ниже приведен 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 FilesPython39python.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:UsersuserAppDataRoamingJetBrainsPyCharmCE2020.3scratchesscratch.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
 

Комментарии:

1. Не должно иметь значения, в каком порядке выполняются ваши тесты — это говорит о том, что вы не в состоянии правильно очистить изменения состояния. Разве этот метод класса не должен создавать новый экземпляр, а не изменять состояние самого класса?

2. Я думаю, что создание нового экземпляра вызывает ту же проблему. Я пойду и проверю сейчас.

Ответ №1:

Здесь происходит то, что у вашего класса есть свойство с именем my_val . Но ваш from_dict метод класса переопределяет его и my_val становится обычным атрибутом (больше не свойством).

Это продемонстрировано в приведенном ниже коде.

Свойства могут быть сложными, вы можете прочитать этот блог, который я написал на эту тему здесь.

 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'>
 

Комментарии:

1. Чтобы продолжить это, правильный способ инициализации вашего метода classmethod будет следующим. Создание временного экземпляра класса ( myclass = cls() ), за которым следует предпочтительное назначение ( myclass.my_val = dict_['my_val'] ) и, наконец, возврат экземпляра класса ( return myclass ) .

2. Да, я понял это, когда увидел в окне структуры в PyCharm, в моем классе было несколько дополнительных полей. Я изменил метод класса, и теперь все работает отлично. 🙂