Patching a method without changing how the method works? - python

I'm trying to test that a pandas method gets called with some values.
However, just by applying a #patch decorator causes the patched method to throw a ValueError within pandas, when the actual method does not. I'm just trying to test that Stock.calc_sma is calling the underlying pandas.rolling_mean function.
I'm under the assumption that the #patch decorator basically adds some "magic" methods to the thing I'm patching that allow me to check if the function was called. If this is the case, why doesn't the pandas.rolling_mean function behave the same whether it's patched vs. not patched?
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()
ERROR: 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

>>> 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
By default patch replaces things with really generic mock objects. As you can see, calling the mock just returns another mock. It has a len of 0 even if the replaced object wouldn't have a len. Its attributes are also generic mocks.
So to simulate behavior requires things extra arguments like:
>>> with patch('os.getcwd', return_value='/a/wonderful/place'):
... os.getcwd()
...
'/a/wonderful/place'
Or to "pass through":
>>> _cwd = os.getcwd
>>> with patch('os.getcwd') as p:
... p.side_effect = lambda: _cwd()
... print(os.getcwd())
...
/
There is a similar example in https://docs.python.org/3.5/library/unittest.mock-examples.html

Related

Subclass of Scipy.Stats.exponweib not calling __init__ or other functions

I am attempting to make a subclass to simplify the inputs to the scipy.stats.exponweib package as well as add some extra functions. Simplifed the class is in its own files call weibull.py
from scipy.stats import exponweib
# import scipy
class weibull(exponweib):
def __init__(self,beta,nu):
super(weibull,self).__init__(a=1,c=beta,loc=0,scale=nu)
print beta
print nu
def doSomething(self,s):
print(s)
My test script looks something like:
from weibull import weibull
w = weibull(2.6,2600)
print('%0.10f'%w.pdf(1000))
w.doSomething('Something')
It seems as though my __init__ is not running at all, none of the print statements run and an error is thrown on the doSomething routine.
The output in the terminal looks like:
Codes> python weibull_testHarness.py
0.0000000000
Traceback (most recent call last):
File "weibull_testHarness.py", line 5, in <module>
w.doSomething('Something')
AttributeError: 'rv_frozen' object has no attribute 'doSomething'
Per NumPy/SciPy developer Robert Kern's answer, subclass rv_frozen, not exponweib.
Notice that exponweib is an instance of the class exponweib_gen.
In [110]: stats.exponweib
Out[110]: <scipy.stats._continuous_distns.exponweib_gen at 0x7fd799db2588>
exponweib is itself a callable which returns an instance of rv_frozen.
In [107]: exponweib(a=1, c=2.6)
Out[107]: <scipy.stats._distn_infrastructure.rv_frozen at 0x7fd7997282b0>
So following this pattern, by analogy, w = weibull(2.6, 2600) will be an instance of rv_frozen. If you want w to have additional methods, then you'll need to subclass rv_frozen, not exponweib, nor exponweib_gen, nor rv_continuous.
import scipy.stats as stats
class my_frozen(stats._distn_infrastructure.rv_frozen):
def __init__(self, dist, *args, **kwds):
super(my_frozen,self).__init__(dist, *args, **kwds)
print(kwds)
def doSomething(self,s):
print(s)
def weibull(beta, nu):
dist = stats.exponweib # an instance of stats._continuous_distns.exponweib_gen
dist.name = 'weibull'
return my_frozen(dist, a=1, c=beta, loc=0, scale=nu)
w = weibull(2.6, 2600)
print('%0.10f'%w.pdf(1000))
w.doSomething('Something')
yields
{'loc': 0, 'scale': 2600, 'c': 2.6, 'a': 1}
0.0001994484
Something

How to extend datetime.timedelta?

I am trying to extend the Python datetime.timedelta for use with cross country race results. I want to construct an object from a string in format u"mm:ss.s". I am able to accomplish this using the factory design pattern and #classmethod annotation. How would I accomplish the same by overriding __init__ and/or __new__?
With the code below, constructing an object raises a TypeError. Note that __init__ is not called, because 'in my __init__' is not printed.
import datetime
import re
class RaceTimedelta(datetime.timedelta):
def __init__(self, timestr = ''):
print 'in my __init__'
m = re.match(r'(\d+):(\d+\.\d+)', timestr)
if m:
mins = int(m.group(1))
secs = float(m.group(2))
super(RaceTimedelta, self).__init__(minutes = mins, seconds = secs)
else:
raise ValueError('timestr not in format u"mm:ss.d"')
Here is the error:
>>> from mytimedelta import RaceTimedelta
>>> RaceTimedelta(u'24:45.7')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported type for timedelta days component: unicode
>>>
If I move my code from __init__ to __new__, I get the following. Note that this time, the output shows that my __new__ function is called.
>>> RaceTimedelta(u'24:45.7')
in my __new__
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "mytimedelta.py", line 16, in __new__
super(RaceTimedelta, self).__new__(minutes = mins, seconds = secs)
TypeError: datetime.timedelta.__new__(): not enough arguments
>>>
Apparently timedelta objects are immutable, which means their value is actually set in the class' __new__() method—so you'll need to override that method instead of its __init__():
import datetime
import re
class RaceTimedelta(datetime.timedelta):
def __new__(cls, timestr=''):
m = re.match(r'(\d+):(\d+\.\d+)', timestr)
if m:
mins, secs = int(m.group(1)), float(m.group(2))
return super(RaceTimedelta, cls).__new__(cls, minutes=mins, seconds=secs)
else:
raise ValueError('timestr argument not in format "mm:ss.d"')
print RaceTimedelta(u'24:45.7')
Output:
0:24:45.700000
BTW, I find it odd that you're providing a default value for thetimestrkeyword argument that will be considered illegal and raise aValueError.

How can I make mock.mock_open raise an IOError?

I need to test a instance method that calls open. In the first test case, I set mock.mock_open to return a string, as expected. This works wonderfully.
However, I also need to test the case in which an IOError is thrown from this function. How can I make mock.mock_open raise an arbitrary exception?
This is my approach so far:
#mock.patch.object(somemodule, 'generateDefaultKey')
def test_load_privatekey(self, genkey)
mo = mock.mock_open(read_data=self.key)
mo.side_effect = IOError
with mock.patch('__main__.open', mo, create=True):
self.controller.loadPrivkey()
self.assertTrue(genkey.called, 'Key failed to regenerate')
Assign the exception to mock.mock_open.side_effect:
mock.mock_open.side_effect = IOError
From the mock.Mock.side_effect documentation:
This can either be a function to be called when the mock is called, or an exception (class or instance) to be raised.
Demo:
>>> mock = MagicMock()
>>> mock.mock_open.side_effect = IOError()
>>> mock.mock_open()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/mj/Development/venvs/stackoverflow-2.7/lib/python2.7/site-packages/mock.py", line 955, in __call__
return _mock_self._mock_call(*args, **kwargs)
File "/Users/mj/Development/venvs/stackoverflow-2.7/lib/python2.7/site-packages/mock.py", line 1010, in _mock_call
raise effect
IOError
When using patch() as a context manager, a new mock object is produced; assign to that mock object:
with mock.patch('__main__.open', mo, create=True) as mocked_open:
mocked_open.side_effect = IOError()
self.controller.loadPrivkey()
I found Martijn's answer a little hard to generalize, so here is what I hope is a simpler answer less tied to the OPs initial code:
from unittest.mock import patch, mock_open
def func_to_test():
try:
with open('path/to/open', 'r') as file:
return 1
except IOError:
return 2
def test_open_error():
with patch("builtins.open", mock_open()) as mock_file:
mock_file.side_effect = IOError()
assert func_to_test() == 2
mock_file.assert_called_with('path/to/open', 'r')

Python unittesting initiate values

Sorry if this question is stupid. I created an unittest class which needs to take given inputs and outputs from outside. Thus, I guess these values should be initiated. However, I met some errors in the following code:
CODE:
import unittest
from StringIO import StringIO
##########Inputs and outputs from outside#######
a=[1,2]
b=[2,3]
out=[3,4]
####################################
def func1(a,b):
return a+b
class MyTestCase(unittest.TestCase):
def __init__(self,a,b,out):
self.a=a
self.b=b
self.out=out
def testMsed(self):
for i in range(self.tot_iter):
print i
fun = func1(self.a[i],self.b[i])
value = self.out[i]
testFailureMessage = "Test of function name: %s iteration: %i expected: %i != calculated: %i" % ("func1",i,value,fun)
self.assertEqual(round(fun,3),round(value,3),testFailureMessage)
if __name__ == '__main__':
f = MyTestCase(a,b,out)
from pprint import pprint
stream = StringIO()
runner = unittest.TextTestRunner(stream=stream, verbosity=2)
result = runner.run(unittest.makeSuite(MyTestCase(a,b,out)))
print 'Tests run', result.testsRun
However, I got the following error
Traceback (most recent call last):
File "C:testing.py", line 33, in <module>
result = runner.run(unittest.makeSuite(MyTestCase(a,b,out)))
File "C:\Python27\lib\unittest\loader.py", line 310, in makeSuite
return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromTestCase(testCaseClass)
File "C:\Python27\lib\unittest\loader.py", line 50, in loadTestsFromTestCase
if issubclass(testCaseClass, suite.TestSuite):
TypeError: issubclass() arg 1 must be a class
Can anyone give me some suggestions? Thanks!
The root of the problem is this line,
result = runner.run(unittest.makeSuite(MyTestCase(a,b,out)))
unittest.makeSuite expects a class, not an instance of a class. So just MyTestCase, not MyTestCase(a, b, out). This means that you can't pass parameters to your test case in the manner you are attempting to. You should probably move the code from init to a setUp function. Either access a, b, and out as globals inside setUp or take a look at this link for information regarding passing parameters to a unit test.
By the way, here is the source file within python where the problem originated. Might be informative to read.

How do I know which contract failed with Python's contract.py?

I'm playing with contract.py, Terrence Way's reference implementation of design-by-contract for Python. The implementation throws an exception when a contract (precondition/postcondition/invariant) is violated, but it doesn't provide you a quick way of identifying which specific contract has failed if there are multiple ones associated with a method.
For example, if I take the circbuf.py example, and violate the precondition by passing in a negative argument, like so:
circbuf(-5)
Then I get a traceback that looks like this:
Traceback (most recent call last):
File "circbuf.py", line 115, in <module>
circbuf(-5)
File "<string>", line 3, in __assert_circbuf___init___chk
File "build/bdist.macosx-10.5-i386/egg/contract.py", line 1204, in call_constructor_all
File "build/bdist.macosx-10.5-i386/egg/contract.py", line 1293, in _method_call_all
File "build/bdist.macosx-10.5-i386/egg/contract.py", line 1332, in _call_all
File "build/bdist.macosx-10.5-i386/egg/contract.py", line 1371, in _check_preconditions
contract.PreconditionViolationError: ('__main__.circbuf.__init__', 4)
My hunch is that the second argument in the PreconditionViolationError (4) refers to the line number in the circbuf.init docstring that contains the assertion:
def __init__(self, leng):
"""Construct an empty circular buffer.
pre::
leng > 0
post[self]::
self.is_empty() and len(self.buf) == leng
"""
However, it's a pain to have to open the file and count the docstring line numbers. Does anybody have a quicker solution for identifying which contract has failed?
(Note that in this example, there's a single precondition, so it's obvious, but multiple preconditions are possible).
This is an old question but I may as well answer it. I added some output, you'll see it at the comment # jlr001. Add the line below to your contract.py and when it raises an exception it will show the doc line number and the statement that triggered it. Nothing more than that, but it will at least stop you from needing to guess which condition triggered it.
def _define_checker(name, args, contract, path):
"""Define a function that does contract assertion checking.
args is a string argument declaration (ex: 'a, b, c = 1, *va, **ka')
contract is an element of the contracts list returned by parse_docstring
module is the containing module (not parent class)
Returns the newly-defined function.
pre::
isstring(name)
isstring(args)
contract[0] in _CONTRACTS
len(contract[2]) > 0
post::
isinstance(__return__, FunctionType)
__return__.__name__ == name
"""
output = StringIO()
output.write('def %s(%s):\n' % (name, args))
# ttw001... raise new exception classes
ex = _EXCEPTIONS.get(contract[0], 'ContractViolationError')
output.write('\tfrom %s import forall, exists, implies, %s\n' % \
(MODULE, ex))
loc = '.'.join([x.__name__ for x in path])
for c in contract[2]:
output.write('\tif not (')
output.write(c[0])
# jlr001: adding conidition statement to output message, easier debugging
output.write('): raise %s("%s", %u, "%s")\n' % (ex, loc, c[1], c[0]))
# ...ttw001
# ttw016: return True for superclasses to use in preconditions
output.write('\treturn True')
# ...ttw016
return _define(name, output.getvalue(), path[0])
Without modifying his code, I don't think you can, but since this is python...
If you look for where he raises the exception to the user, it I think is possible to push the info you're looking for into it... I wouldn't expect you to be able to get the trace-back to be any better though because the code is actually contained in a comment block and then processed.
The code is pretty complicated, but this might be a block to look at - maybe if you dump out some of the args you can figure out whats going on...
def _check_preconditions(a, func, va, ka):
# ttw006: correctly weaken pre-conditions...
# ab002: Avoid generating AttributeError exceptions...
if hasattr(func, '__assert_pre'):
try:
func.__assert_pre(*va, **ka)
except PreconditionViolationError, args:
# if the pre-conditions fail, *all* super-preconditions
# must fail too, otherwise
for f in a:
if f is not func and hasattr(f, '__assert_pre'):
f.__assert_pre(*va, **ka)
raise InvalidPreconditionError(args)
# rr001: raise original PreconditionViolationError, not
# inner AttributeError...
# raise
raise args
# ...rr001
# ...ab002
# ...ttw006

Categories

Resources