Почему невозможно применить декоратор к функции?

Как можно исправить код для юнит-теста, чтобы обойти декоратор, который проверяет типы, и использовать Mock объекты для успешного тестирования функции call класса Calculator?
  • 29 марта 2024 г. 10:29
Ответы на вопрос 3
Невозможно применить декоратор к функции, потому что декоратор обычно применяется к функции в момент её определения, чтобы изменить её поведение. Если декоратор проверяет типы аргументов или возвращает результат, то происходит валидация входных данных до выполнения функции, и, если типы не совпадают или допущена ошибка, функция просто не будет вызвана.

Для обхода декоратора и успешного тестирования можно использовать Mock объекты из библиотеки unittest.mock. Например, если у нас есть класс Calculator с методом call, к которому применен декоратор проверки типов аргументов:

```python
from unittest.mock import patch

class Calculator:
    @type_checker
    def call(self, a: int, b: int) -> int:
        return a + b
```

Чтобы обойти декоратор и протестировать метод call с помощью Mock объектов, можно использовать следующий код в юнит-тесте:

```python
import unittest
from unittest.mock import MagicMock

class TestCalculator(unittest.TestCase):

    @patch('__main__.type_checker')
    def test_call(self, mock_checker):
        calculator = Calculator()
        mock_checker.return_value = lambda func: func
        
        result = calculator.call(1, 2)
        
        self.assertEqual(result, 3)
```

При использовании Mock объекта mock_checker вместо реального декоратора type_checker, мы обходим проверку типов и успешно тестируем функциональность метода call.
Мне кажется, ты просто используешь неверный инструмент, пытаясь совместить несовместимое. 
Вариант А
Ты проверяешь, что у тебя объект нужного типа. Тогда твой декоратор в принципе работает, но вообще-то это задача для статического анализатора кода типа mypy или встроенного в pycharm, а не проверки в рантайме. И тогда нужно забыть про моки.

Вариант Б
Ты проверяешь, что у тебя объект имеет нужные поля и методы (duck typing).
Тогда тебе нужен typing.Protocol в комбинации с typing.runtime_checkable, с помощью которого ты сможешь описать, что должен иметь объект. Затем этот протокол можно будет подсунуть в isinstance() для проверки, и мок, по идее, её пройдёт. Но опять-таки, задача скорее для статического анализатора кода, чем для рантайм-проверки. Если у тебя в принципе неведомо что может быть передано в метод - это простыми тестами не решается.
А ларчик то просто открывался 
import unittest
from unittest.mock import MagicMock, patch
from calculator import Calculator
from mega_number import MegaNumber


class TestCalculator(unittest.TestCase):

    def test_call(self):
        mock_mega_number = MagicMock(MegaNumber)
        mock_mega_number.value = 10
        calculator = Calculator()
        result = calculator.call(mock_mega_number, 5)

        self.assertEqual(result, 15, "The call method should return the sum of MegaNumber's "
                                     "value and the integer provided.")
Похожие вопросы