如何在不改变方法功能的情况下修补方法?

1 投票
1 回答
1993 浏览
提问于 2025-04-18 15:45

我正在尝试测试一个pandas的方法是否被调用,并且传入了一些值。

但是,仅仅使用一个@patch装饰器就导致被替换的方法在pandas中抛出了一个ValueError错误,而实际的方法并不会出现这个错误。我只是想测试一下Stock.calc_sma是否调用了底层的pandas.rolling_mean函数。

我认为@patch装饰器基本上是给我想替换的东西添加了一些“魔法”方法,这样我就可以检查这个函数是否被调用。如果真是这样,为什么pandas.rolling_mean函数在被替换和不被替换的情况下表现不一样呢?

app/models.py

import pandas as pd
class Stock:  # i've excluded a bunch of class methods, including the one that sets self.data, which is a DataFrame of stock prices.
    def calc_sma(self, num_days)
        if self.data.shape[0] > num_days:  # Stock.data holds a DataFrame of stock prices
                column_title = 'sma' + str(num_days)
                self.data[column_title] = pd.rolling_mean(self.data['Adj Close'], num_days)

app/tests/TestStockModel.py

def setUp(self):
    self.stock = MagicMock(Stock)
    self.stock.ticker = "AAPL"
    self.stock.data = DataFrame(aapl_test_data.data)

@patch('app.models.pd.rolling_mean')
def test_calc_sma(self, patched_rolling_mean):
    Stock.calc_sma(self.stock, 3)
    assert(isinstance(self.stock.data['sma3'], Series))
    patched_rolling_mean.assert_any_call()

错误:test_calc_sma (TestStockModel.TestStockModel)

Traceback (most recent call last):
  File "/Users/grant/Code/python/chartflux/env/lib/python2.7/site-packages/mock.py", line 1201, in patched
    return func(*args, **keywargs)
  File "/Users/grant/Code/python/chartflux/app/tests/TestStockModel.py", line 26, in test_calc_sma
    Stock.calc_sma(self.stock, 3)
  File "/Users/grant/Code/python/chartflux/app/models.py", line 27, in calc_sma
    self.data[column_title] = pd.rolling_mean(self.data['Adj Close'], num_days)
  File "/Users/grant/Code/python/chartflux/env/lib/python2.7/site-packages/pandas/core/frame.py", line 1887, in __setitem__
    self._set_item(key, value)
  File "/Users/grant/Code/python/chartflux/env/lib/python2.7/site-packages/pandas/core/frame.py", line 1967, in _set_item
    value = self._sanitize_column(key, value)
  File "/Users/grant/Code/python/chartflux/env/lib/python2.7/site-packages/pandas/core/frame.py", line 2017, in _sanitize_column
    raise ValueError('Length of values does not match length of '
ValueError: Length of values does not match length of index

1 个回答

6
>>> import os
>>> os.getcwd()
'/'
>>> from unittest.mock import patch
>>> with patch('os.getcwd'):
...     print(os.getcwd)
...     print(os.getcwd())
...     print(len(os.getcwd()))
...
<MagicMock name='getcwd' id='4472112296'>
<MagicMock name='getcwd()' id='4472136928'>
0

默认情况下,patch 会用一些非常普通的模拟对象来替换东西。你可以看到,调用这个模拟对象只会返回另一个模拟对象。即使被替换的对象本来没有 len,这个模拟对象的 len 也是 0。它的属性也是普通的模拟对象。

所以,要模拟真实的行为,就需要额外的参数,比如:

>>> with patch('os.getcwd', return_value='/a/wonderful/place'):
...     os.getcwd()
...
'/a/wonderful/place'

或者要“直接通过”:

>>> _cwd = os.getcwd
>>> with patch('os.getcwd') as p:
...     p.side_effect = lambda: _cwd()
...     print(os.getcwd())
...
/

https://docs.python.org/3.5/library/unittest.mock-examples.html 里有一个类似的例子。

撰写回答