Как я могу издеваться над функцией, которая не вызывается по имени? - PullRequest
1 голос
/ 21 апреля 2020

Предположим, у меня есть декоратор, который собирает все функции, которые он украшает, для вызова в будущем.

mydecorator.py

class CallLater(object):
    funcs = []

    def __init__(self, func):
        self.funcs.append(func)

    @classmethod
    def call_now(cls, *args, **kwargs):
        for func in cls.funcs:
            func(*args, **kwargs)

Тогда , У меня есть функция в модуле, одна из которых будет сохранена моим декоратором.

mymodule.py

import logging

from mydecorator import CallLater

logging.basicConfig(level=logging.INFO) 

@CallLater
def log_a():
    logging.info("A")

@CallLater
def log_b():
    logging.info("B")

def log_c():
    logging.info("C")

Теперь, если я импортирую mymodule и позвоните CallLater.call_now(), log_a и log_b. Но скажем, что во время тестирования я хочу заменить log_b на log_c. Я постараюсь сделать замену с помощью макета.

mock_test.py

import logging
import pytest

from mymodule import log_a, log_c
from mydecorator import CallLater

logging.basicConfig(level=logging.INFO) 
pytest_plugins = ('pytest_mock',)

def test_mocking(mocker, caplog):
    mocker.patch('mymodule.log_b', log_c)

    CallLater.call_now()

    logs = [rec.message for rec in caplog.records]
    assert logs == ["A", "C"]

Но когда я запускаю pytest, я вижу, что мой макет не не работает.

FAILED mock_test.py::test_mocking - AssertionError: assert ['A', 'B'] == ['A', 'C']

Я думаю, что 'mymodule.log_b' - неправильная цель для насмешки, так как она не вызывается как mymodule.log_b(), но я не уверен, что использовать вместо этого в этой ситуации. Любой совет приветствуется!

1 Ответ

1 голос
/ 21 апреля 2020

Это невозможно как таковое, проблема в том, что функции уже назначены списку во время загрузки. Единственный способ увидеть, как это можно исправить - это напрямую CallLater.funcs, что немного неловко, потому что вам нужно вручную заменить log_b на log_c - но здесь все идет так:

import logging
from unittest import mock

from mymodule import log_c
from mydecorator import CallLater

logging.basicConfig(level=logging.INFO)


def test_mocking(caplog):
    funcs = [log_c if f.__name__ == 'log_b' else f for f in CallLater.funcs]
    with mock.patch.object(CallLater, 'funcs', funcs):
        CallLater.call_now()

        logs = [rec.message for rec in caplog.records]
        assert logs == ["A", "C"]

Обратите внимание, что вы не можете сравнивать напрямую с функцией (например, f == log_b), потому что log_b - это декорированная функция, а не функция, сохраненная в CallLater.funcs.

...