Suppose f conditionally calls print; I'd like to know whether this happens within test_*(). How can this be accomplished?
Example:
def f(integer): # defined in and imported from separate module
if isinstance(integer, str):
print("WARNING: integer is str")
def test_f():
f("5")
assert print.called
Attempted approach:
def tracked_call(self, *args, **kwargs):
self.called = True
self.__call__(*args, **kwargs)
print.__call__ = tracked_call
>>> AttributeError: 'builtin_function_or_method' object attribute '__call__' is read-only
Solution 1 (best): check that print was called, and that it prints specific text; doesn't use a fixture:
import builtins
import contextlib, io
from unittest.mock import Mock
def test_f():
mock = Mock()
mock.side_effect = print # ensure actual print is called to capture its txt
print_original = print
builtins.print = mock
try:
str_io = io.StringIO()
with contextlib.redirect_stdout(str_io):
f("5")
output = str_io.getvalue()
assert print.called # `called` is a Mock attribute
assert output.startswith("WARNING:")
finally:
builtins.print = print_original # ensure print is "unmocked"
(If print in f writes to sys.stderr instead of the default sys.stdout, use contextlib.redirect_stderr.)
Solution 2: check that print prints specific text within call; from docs:
def test_f(capsys):
f("5")
out, err = capsys.readouterr()
assert out.startswith("WARNING:")
This assuming the default print(file=sys.stdout), else the string of interest is in err. If specific text is of no interest, can do assert out or err to verify that something was printed. This doesn't necessarily test whether print was called, as we can do print(end='').
Related
I'm using python unittest for functions that write data to JSON. I use tearDownClass to delete the output test files so they don't clutter the local repo. Ground truths are also stored as JSON files.
I do want to store the output test files when tests fail, so its easier for troubleshooting.
My current implementation is to use a global boolean keep_file = False. When the unittest fails the assertion, it modifies keep_file = True. tearDownClass only deletes the files when keep_file == False. I don't like the idea of modifying global variables and the try exception blocks for each assert.
import json
import os
import unittest
from src.mymodule import foo1, foo2
# These are defined outside the class on purpose so the classmethods can access them
FILE_1 = "unittest.file1.json"
EXPECTED_FILE_1 = "expected.file1.json"
FILE_2 = "unittest.file2.json"
EXPECTED_FILE_2 = "expected.file2.json"
keep_files = False
class TestRhaPostPayload(unittest.TestCase):
#classmethod
def setUpClass(cls):
cls.get_file1()
cls.get_file2()
#classmethod
def get_file1(cls):
output1 = foo1()
with open(FILE_1, "w") as f:
f.write(output1)
#classmethod
def get_file2(cls):
output2 = foo1()
with open(FILE_2, "w") as f:
f.write(output2)
#classmethod
def tearDownClass(cls):
if not keep_files:
os.remove(FILE_1)
os.remove(FILE_2)
def test_foo1(self):
# code that reads in file1 and expected_file_1
try:
self.assert(expected_output1, output1)
except AssertionError:
global keep_files
keep_files = True
raise
def test_foo2(self):
# code that reads in file2 and expected_file_2
try:
self.assert(expected_output2, output2)
except AssertionError:
global keep_files
keep_files = True
raise
You could simply check, if there were any errors/failures in your test case during tear-down and only delete the files, if there were none.
How to perform this check was explained in this post.
This check is done on a TestCase instance so tearDownClass won't work. But you are using different files in different tests anyway, so you might as well use normal setUp/tearDown to remove the current file.
Here is a working example:
from pathlib import Path
from typing import Optional
from unittest import TestCase
class Test(TestCase):
def all_tests_passed(self) -> bool:
"""Returns `True` if no errors/failures occurred at the time of calling."""
outcome = getattr(self, "_outcome")
if hasattr(outcome, "errors"): # Python <=3.10
result = self.defaultTestResult()
getattr(self, "_feedErrorsToResult")(result, outcome.errors)
else: # Python >=3.11
result = outcome.result
return all(test != self for test, _ in result.errors + result.failures)
def setUp(self) -> None:
super().setUp()
self.test_file: Optional[Path] = None
def tearDown(self) -> None:
super().tearDown()
if self.test_file and self.all_tests_passed():
self.test_file.unlink()
def test_foo(self) -> None:
self.test_file = Path("foo.txt")
self.test_file.touch()
self.assertTrue(True)
def test_bar(self) -> None:
self.test_file = Path("bar.txt")
self.test_file.touch()
self.assertTrue(False)
Running this test case leaves bar.txt in the current working directory, whereas foo.txt is gone.
I'm trying to test some code that operates on a file, and I can't seem to get my head around how to replace using a real file with mock and io.StringIO
My code is pretty much the following:
class CheckConfig(object):
def __init__(self, config):
self.config = self._check_input_data(config)
def _check_input_data(self, data):
if isinstance(data, list):
return self._parse(data)
elif os.path.isfile(data):
with open(data) as f:
return self._parse(f.readlines())
def _parse(self, data):
return data
I have a class that can take either a list or a file, if it's a file it opens it and extracts the contents into a list, and then does what it needs to do to the resulting list.
I have a working test as follows:
def test_CheckConfig_with_file():
config = 'config.txt'
expected = parsed_file_data
actual = CheckConfig(config).config
assert expected == actual
I want to replace the call to the filesystem. I have tried replacing the file with io.StringIO but I get a TypeError from os.path.isfile() as it's expecting either a string, bytes or int. I also tried mocking the isfile method like so:
#mock.patch('mymodule.os.path')
def test_CheckConfig_with_file(mock_path):
mock_path.isfile.return_value = True
config = io.StringIO('data')
expected = parsed_file_data
actual = CheckConfig(config).config
assert expected == actual
but I still get the same TypeError as the _io.StringIO type is causing the exception before isfile gets a chance to return something.
How can I get os.path.isfile to return True, when I pass it a fake file? Or is this a suggestion I should change my code?
Just mock out both os.path.isfile and the open() call, and pass in a fake filename (you are not expected to pass in an open file, after all).
The mock library includes a utility for the latter: mock_open():
#mock.patch('os.path.isfile')
def test_CheckConfig_with_file(mock_isfile):
mock_isfile.return_value = True
config_data = mock.mock_open(read_data='data')
with mock.patch('mymodule.open', config_data) as mock_open:
expected = parsed_file_data
actual = CheckConfig('mocked/filename').config
assert expected == actual
This causes the if isinstance(data, list): test to be false (because data is a string instead), followed by the elif os.path.isfile(data): returning True, and the open(data) call to use your mocked data from the mock_open() result.
You can use the mock_open variable to assert that open() was called with the right data (mock_open. assert_called_once_with('mocked/filename') for example).
Demo:
>>> import os.path
>>> from unittest import mock
>>> class CheckConfig(object):
... def __init__(self, config):
... self.config = self._check_input_data(config)
... def _check_input_data(self, data):
... if isinstance(data, list):
... return self._parse(data)
... elif os.path.isfile(data):
... with open(data) as f:
... return self._parse(f.readlines())
... def _parse(self, data):
... return data
...
>>> with mock.patch('os.path.isfile') as mock_isfile:
... mock_isfile.return_value = True
... config_data = mock.mock_open(read_data='line1\nline2\n')
... with mock.patch('__main__.open', config_data) as mock_open:
... actual = CheckConfig('mocked/filename').config
...
>>> actual
['line1\n', 'line2\n']
>>> mock_open.mock_calls
[call('mocked/filename'),
call().__enter__(),
call().readlines(),
call().__exit__(None, None, None)]
In case you end up here wondering how to solve this using the pytest-mock library, here is how you do it:
def test_open(mocker):
m = mocker.patch('builtins.open', mocker.mock_open(read_data='bibble'))
with open('foo') as h:
result = h.read()
m.assert_called_once_with('foo')
assert result == 'bibble'
This code example was found (but had to be adjusted) here.
Python: How to get the caller's method name in the called method?
Assume I have 2 methods:
def method1(self):
...
a = A.method2()
def method2(self):
...
If I don't want to do any change for method1, how to get the name of the caller (in this example, the name is method1) in method2?
inspect.getframeinfo and other related functions in inspect can help:
>>> import inspect
>>> def f1(): f2()
...
>>> def f2():
... curframe = inspect.currentframe()
... calframe = inspect.getouterframes(curframe, 2)
... print('caller name:', calframe[1][3])
...
>>> f1()
caller name: f1
this introspection is intended to help debugging and development; it's not advisable to rely on it for production-functionality purposes.
Shorter version:
import inspect
def f1(): f2()
def f2():
print 'caller name:', inspect.stack()[1][3]
f1()
(with thanks to #Alex, and Stefaan Lippen)
This seems to work just fine:
import sys
print sys._getframe().f_back.f_code.co_name
I would use inspect.currentframe().f_back.f_code.co_name. Its use hasn't been covered in any of the prior answers which are mainly of one of three types:
Some prior answers use inspect.stack but it's known to be too slow.
Some prior answers use sys._getframe which is an internal private function given its leading underscore, and so its use is implicitly discouraged.
One prior answer uses inspect.getouterframes(inspect.currentframe(), 2)[1][3] but it's entirely unclear what [1][3] is accessing.
import inspect
from types import FrameType
from typing import cast
def demo_the_caller_name() -> str:
"""Return the calling function's name."""
# Ref: https://stackoverflow.com/a/57712700/
return cast(FrameType, cast(FrameType, inspect.currentframe()).f_back).f_code.co_name
if __name__ == '__main__':
def _test_caller_name() -> None:
assert demo_the_caller_name() == '_test_caller_name'
_test_caller_name()
Note that cast(FrameType, frame) is used to satisfy mypy.
Acknowlegement: comment by 1313e for an answer.
I've come up with a slightly longer version that tries to build a full method name including module and class.
https://gist.github.com/2151727 (rev 9cccbf)
# Public Domain, i.e. feel free to copy/paste
# Considered a hack in Python 2
import inspect
def caller_name(skip=2):
"""Get a name of a caller in the format module.class.method
`skip` specifies how many levels of stack to skip while getting caller
name. skip=1 means "who calls me", skip=2 "who calls my caller" etc.
An empty string is returned if skipped levels exceed stack height
"""
stack = inspect.stack()
start = 0 + skip
if len(stack) < start + 1:
return ''
parentframe = stack[start][0]
name = []
module = inspect.getmodule(parentframe)
# `modname` can be None when frame is executed directly in console
# TODO(techtonik): consider using __main__
if module:
name.append(module.__name__)
# detect classname
if 'self' in parentframe.f_locals:
# I don't know any way to detect call from the object method
# XXX: there seems to be no way to detect static method call - it will
# be just a function call
name.append(parentframe.f_locals['self'].__class__.__name__)
codename = parentframe.f_code.co_name
if codename != '<module>': # top level usually
name.append( codename ) # function or a method
## Avoid circular refs and frame leaks
# https://docs.python.org/2.7/library/inspect.html#the-interpreter-stack
del parentframe, stack
return ".".join(name)
Bit of an amalgamation of the stuff above. But here's my crack at it.
def print_caller_name(stack_size=3):
def wrapper(fn):
def inner(*args, **kwargs):
import inspect
stack = inspect.stack()
modules = [(index, inspect.getmodule(stack[index][0]))
for index in reversed(range(1, stack_size))]
module_name_lengths = [len(module.__name__)
for _, module in modules]
s = '{index:>5} : {module:^%i} : {name}' % (max(module_name_lengths) + 4)
callers = ['',
s.format(index='level', module='module', name='name'),
'-' * 50]
for index, module in modules:
callers.append(s.format(index=index,
module=module.__name__,
name=stack[index][3]))
callers.append(s.format(index=0,
module=fn.__module__,
name=fn.__name__))
callers.append('')
print('\n'.join(callers))
fn(*args, **kwargs)
return inner
return wrapper
Use:
#print_caller_name(4)
def foo():
return 'foobar'
def bar():
return foo()
def baz():
return bar()
def fizz():
return baz()
fizz()
output is
level : module : name
--------------------------------------------------
3 : None : fizz
2 : None : baz
1 : None : bar
0 : __main__ : foo
You can use decorators, and do not have to use stacktrace
If you want to decorate a method inside a class
import functools
# outside ur class
def printOuterFunctionName(func):
#functools.wraps(func)
def wrapper(self):
print(f'Function Name is: {func.__name__}')
func(self)
return wrapper
class A:
#printOuterFunctionName
def foo():
pass
you may remove functools, self if it is procedural
An alternative to sys._getframe() is used by Python's Logging library to find caller information. Here's the idea:
raise an Exception
immediately catch it in an Except clause
use sys.exc_info to get Traceback frame (tb_frame).
from tb_frame get last caller's frame using f_back.
from last caller's frame get the code object that was being executed in that frame.
In our sample code it would be method1 (not method2) being executed.
From code object obtained, get the object's name -- this is caller method's name in our sample.
Here's the sample code to solve example in the question:
def method1():
method2()
def method2():
try:
raise Exception
except Exception:
frame = sys.exc_info()[2].tb_frame.f_back
print("method2 invoked by: ", frame.f_code.co_name)
# Invoking method1
method1()
Output:
method2 invoked by: method1
Frame has all sorts of details, including line number, file name, argument counts, argument type and so on. The solution works across classes and modules too.
Code:
#!/usr/bin/env python
import inspect
called=lambda: inspect.stack()[1][3]
def caller1():
print "inside: ",called()
def caller2():
print "inside: ",called()
if __name__=='__main__':
caller1()
caller2()
Output:
shahid#shahid-VirtualBox:~/Documents$ python test_func.py
inside: caller1
inside: caller2
shahid#shahid-VirtualBox:~/Documents$
I found a way if you're going across classes and want the class the method belongs to AND the method. It takes a bit of extraction work but it makes its point. This works in Python 2.7.13.
import inspect, os
class ClassOne:
def method1(self):
classtwoObj.method2()
class ClassTwo:
def method2(self):
curframe = inspect.currentframe()
calframe = inspect.getouterframes(curframe, 4)
print '\nI was called from', calframe[1][3], \
'in', calframe[1][4][0][6: -2]
# create objects to access class methods
classoneObj = ClassOne()
classtwoObj = ClassTwo()
# start the program
os.system('cls')
classoneObj.method1()
Hey mate I once made 3 methods without plugins for my app and maybe that can help you, It worked for me so maybe gonna work for you too.
def method_1(a=""):
if a == "method_2":
print("method_2")
if a == "method_3":
print("method_3")
def method_2():
method_1("method_2")
def method_3():
method_1("method_3")
method_2()
I try to add unit test in python in function that save stats in a file
Here is the function for saving
def save_file_if_necessary(file_path, content, current_time, mode="w", delta_time=60, force=False):
if file_path not in file_save or current_time - file_save[file_path] >= delta_time or force:
with codecs.open(file_path, mode, encoding="utf-8") as written_file:
written_file.write(content)
file_save[file_path] = time.time()
print "yes"
return True
else:
print "not necessary"
return False
I make a call of this function like that
def test_function():
bot_url_dic = {"seven1": 10,
"seven2": 20
}
save_file_if_necessary(os.path.join("./", "recipients.bots"),json.dumps(bot_url_dic, ensure_ascii=False, indent=4), time.time())
And i made some unittest with mock to test if the function is called
from test import save_file_if_necessary, test_function
def test_call_save_file_if_necessary(self):
"""test function to test add in list."""
ip_dic = ["seven1", "seven2", "seven3"]
save_file_if_necessary = Mock()
test_function()
self.assertTrue(save_file_if_necessary.called)
But the problem is Mock is always return False but the function is called at least one time.
self.assertTrue(save_file_if_necessary.called)
AssertionError: False is not true
(python version 2.7.6)
All you've done is create a new Mock object, coincidentally called "save_file_if_necessary". You haven't done anything to replace the actual function with your mock.
You need to use the patch functionality to actually do that:
#mock.patch('my_test_module.save_file_if_necessary')
def test_call_save_file_if_necessary(self, mock_function):
ip_dic = ["seven1", "seven2", "seven3"]
test_function()
self.assertTrue(mock_file.called)
You need to import the module where the function is defined and assign a Mock to your function:
import test
def test_call_save_file_if_necessary(self):
"""test function to test add in list."""
ip_dic = ["seven1", "seven2", "seven3"]
test.save_file_if_necessary = Mock()
test.test_function()
self.assertTrue(test.save_file_if_necessary.called)
Or, use the patching function instead.
as an example here, i want to make a function to temporarily direct the stdout to a log file.
the tricky thing is that the codes have to keep the file handler and std sav for restoration after the redirect, i wrote it in class type to keep these two variables.
here below the full code:
class STDOUT2file:
def __init__(self,prefix='report#'):
now=dt.date.today()
repname=repnameprefix=prefix+now.strftime("%Y%m%d")+'.txt'
count=0
while os.path.isfile(repname):
count+=1
repname=repnameprefix+(".%02d" %(count))
self.sav=sys.stdout
f=open(repname,'w')
sys.stdout=f
self.fname=repname
self.fhr=f
def off(self,msg=False):
sys.stdout=self.sav
self.fhr.close()
if msg:
print('output to:'+self.fname)
return
here is the code to apply it:
outbuf=STDOUT2file()
#codes to print out to stdout
outbuf.off(msg=True)
i want to make it more clean, read about 'closure' but it returns a function at the first call, kind of assigment type as similar as class.
i want it to be like:
STDOUT2file('on')
STDout2file('off',msg=True)
note: redirecting to stdout is an example i encountered just now.. what i am wondering is, any way other than class type to make simple functionality like those on/off type, which involve store/retrieval of state variables that should be better made invisible to outside.
Try using a context manager instead. This idiom is common enough that it was included in the PEP that introduced context managers (slightly modified here):
from contextlib import contextmanager
#contextmanager
def redirect_stdout(new_stdout):
import sys
save_stdout = sys.stdout
sys.stdout = new_stdout
try:
yield
finally:
sys.stdout = save_stdout
Or, if you like, the class-based version with __enter__ and __exit__:
class redirect_stdout:
"""Context manager for temporarily redirecting stdout to another file
docstring truncated
"""
def __init__(self, new_target):
self.new_target = new_target
def __enter__(self):
self.old_target = sys.stdout
sys.stdout = self.new_target
return self.new_target
def __exit__(self, exctype, excinst, exctb):
sys.stdout = self.old_target
Raymond Hettinger actually committed this to contextlib, it will be included in python 3.4 as contextlib.redirect_stdout().
Basic usage:
with open('somelogfile','a') as f:
with stdout_redirected(f):
print(something)
Yes, you can save state information in a function. Just name the variable functionname.something and it will be saved. For example:
def stdout2file(status, prefix='pre', msg=False):
import datetime as dt
import os
import sys
if not hasattr(stdout2file, 'sav'):
stdout2file.sav = None
if status == 'on':
if stdout2file.sav:
print('You have already triggered this once Ignoring this request.')
else:
now = dt.date.today()
repname = repnameprefix = prefix + now.strftime("%Y%m%d") + '.txt'
count = 0
while os.path.isfile(repname):
count += 1
repname = repnameprefix + (".%02d" %(count))
stdout2file.sav = sys.stdout
f = open(repname,'w')
sys.stdout = f
stdout2file.fhr = f
elif status == 'off':
if not stdout2file.sav:
print('Redirect is "off" already. Ignoring this request')
else:
sys.stdout = stdout2file.sav
stdout2file.fhr.close()
if msg:
print('output to:' + stdout2file.fhr.name)
stdout2file.sav = None
else:
print('Unrecognized request')
It is also possible to keep status information in mutable keyword parameters like so:
def stdout_toggle(prefix='pre', msg=False, _s=[None, None]):
import datetime as dt
import os
import sys
if _s[0] is None:
now = dt.date.today()
repname = repnameprefix = prefix + now.strftime("%Y%m%d") + '.txt'
count = 0
while os.path.isfile(repname):
count += 1
repname = repnameprefix + (".%02d" %(count))
f = open(repname,'w')
_s[:] = [sys.stdout, f]
sys.stdout = f
else:
sys.stdout = _s[0]
_s[1].close()
if msg:
print('output to:' + _s[1].name)
_s[:] = [None, None]
The user can call the above without any arguments and it will toggle between the redirect between on and off. The function remembers the current status through the keyword parameter _s which is a mutable list.
Although some consider the fact that mutable keyword parameters are preserved between function calls to be a language flaw, it is consistent with python philosophy. It works because the default values for keyword parameters are assigned when the function is first defined, that is when the def statement is executed, and not when the function is called. Consequently, _s=[None, None] is assigned once at definition and is free to vary thereafter.