How to run the same TestSuite multiple times unittest texttestrunner - python

I want to run tests with multiple builds of a product running them once. Here is example of the code:
import unittest
suite = unittest.TestLoader().discover("./tests")
runner = unittest.TextTestRunner()
for build in [build1, build2]:
get_the_build(build)
runner.run(suite)
The first iteration works well, but on the start of the second one an error appears:
Traceback (most recent call last):
File "D:/Path/to/my/folder/run_tests.py", line 9, in <module>
runner.run(suite)
File "C:\Program Files (x86)\Python36-32\lib\unittest\runner.py", line 176, in run
test(result)
File "C:\Program Files (x86)\Python36-32\lib\unittest\suite.py", line 84, in __call__
return self.run(*args, **kwds)
File "C:\Program Files (x86)\Python36-32\lib\unittest\suite.py", line 122, in run
test(result)
TypeError: 'NoneType' object is not callable
What is happening? What result runner calls? And why does it fail? Any ideas how to solve the problem?

Well, well, well. I have spent the last hour of my life looking at the code of unittest in GitHub, which can be found here. I just went to the code of suite.py (here), one of the files in the error you are getting. This is the actual code of TestSuite.run:
def run(self, result, debug=False):
topLevel = False
if getattr(result, '_testRunEntered', False) is False:
result._testRunEntered = topLevel = True
for index, test in enumerate(self):
if result.shouldStop:
break
if _isnotsuite(test):
self._tearDownPreviousClass(test, result)
self._handleModuleFixture(test, result)
self._handleClassSetUp(test, result)
result._previousTestClass = test.__class__
if (getattr(test.__class__, '_classSetupFailed', False) or
getattr(result, '_moduleSetUpFailed', False)):
continue
if not debug:
test(result)
else:
test.debug()
if self._cleanup:
self._removeTestAtIndex(index)
if topLevel:
self._tearDownPreviousClass(None, result)
self._handleModuleTearDown(result)
result._testRunEntered = False
return result
So, basically, what this code does is to iterate over each test the suite has and invoke it:
for index, test in enumerate(self):
...
if not debug:
test(result) # This is the line throwing the error
...
As you can see, that loop iterates over the suite itself, so I it must have an __iter__ method defined somewhere. After 5 minutes of not finding it inside the class TestSuite, I realized is the parent class who has such method. This is what I found in BaseTestSuite:
def __iter__(self):
return iter(self._tests)
Basically, it just returns an iterator of the tests. In that moment, such line of code, was a high wall I couldn't surepass. But I didn't give up and went back to TestSuite.run definition and, miraculously, I spotted the next lines:
...
if self._cleanup:
self._removeTestAtIndex(index)
...
And that made me wonder: "Are the tests being removed? Let me investigate". Then I was enlightened, because inside _removeTestAtIndex I spotted this line:
self._tests[index] = None
End of story. So, after running all of your tests the first time, they got converted into nothing more than None: the list of tests inside the suite ended up being a list of Nones ([None, None, ..., None]).
So, how do you prevent such behaviour? Just turn off the _cleanup flag inside the suite. This should work:
Answer
import unittest
suite = unittest.TestLoader().discover("./tests")
suite._cleanup = False # Prevent such cleanup
runner = unittest.TextTestRunner()
for build in [build1, build2]:
get_the_build(build)
runner.run(suite)
Sorry for the long story but, besides showing you how to solve your issue, I also wanted to teach you how to debug whatever.
Let me know if this actually worked for you. Otherwise, tell me what went wrong.

Feels like a horrible hack, but making a deep copy of the suite each time before running it solved the problem for me:
import unittest
import copy
suite = unittest.TestLoader().discover("./tests")
runner = unittest.TextTestRunner()
for build in [build1, build2]:
get_the_build(build)
runner.run(copy.deepcopy(suite))

Related

What does a "with" statement do without the "as"?

with dpg.window(label="Window"):
dpg.add_text("Hello, world")
dpg.add_button(label="Save")
dpg.add_input_text(label="string", default_value="Quick brown fox")
dpg.add_slider_float(label="float", default_value=0.273, max_value=1)
dpg.add_color_picker(label="Pick", default_value=(0, 0, 0))
This code runs without error (given the correct imports and setup)
dpg.window(label="Window")
dpg.add_text("Hello, world")
dpg.add_button(label="Save")
dpg.add_input_text(label="string", default_value="Quick brown fox")
dpg.add_slider_float(label="float", default_value=0.273, max_value=1)
dpg.add_color_picker(label="Pick", default_value=(0, 0, 0))
This code does not. A runtime error occurs on line 2. I do not understand how a with statement with no as affects the contents of the with. I've seen another similar post, but I can't understand how the explanation answers my question.
Here is the stacktrace for the error:
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "C:\Users\18328\PycharmProjects\sandbox\main.py", line 10, in <module>
dpg.add_text("Hello, world")
File "C:\Users\18328\AppData\Local\Programs\Python\Python39\lib\site-packages\dearpygui\dearpygui.py", line 7019, in add_text
return internal_dpg.add_text(default_value, label=label, user_data=user_data, use_internal_label=use_internal_label, tag=tag, indent=indent, parent=parent, before=before, source=source, payload_type=payload_type, drag_callback=drag_callback, drop_callback=drop_callback, show=show, pos=pos, filter_key=filter_key, tracked=tracked, track_offset=track_offset, wrap=wrap, bullet=bullet, color=color, show_label=show_label, **kwargs)
SystemError: <built-in function add_text> returned a result with an error set
On the Python side: Context managers run completely arbitrary code on enter and exit. Python doesn't force that code to do things "behind your back", but neither does it present them from doing so; so the implementation of the specific context manager at hand needs to be inspected to know why it's modifying behavior.
From a DearPyGui perspective: The C++ library that dpg backends into maintains a "container stack". Entering a container as a context manager ensures that it's on the top of your stack. When you create a new DearPyGui object that needs to be attached to a container, it refers to that stack, and attaches itself to the thing that is currently on top.
Using the translation provided by PEP-343, you can see that
with dpg.window(label="Window"):
...
is equivalent to
mgr = dpg.window(label="Window")
exit = type(mgr.__exit__)
value = type(mgr).__enter__(mgr)
exc = True
try:
try:
...
except:
exc = False
if not exit(mgr, *sys.exc_info()):
raise
finally:
if exc:
exit(mgr, None, None, None)
You can compare the above to the fuller translation in PEP-343 to see the single line I omitted that results from the use of as.
In your examples, dpg.window(label="Window").__enter__ is called in the first example, but not in the second.

Mocking a static function call with a long chain of calls in python 2.7

I'm trying to test a large legacy Django application, and I'm getting confused by Python mocking as I have never worked on large Python application.
Specifically, I have method has a long call chain inside that generates an array:
def update(self): # in some class X
# ...
for z in foo.models.Bar.objects.filter(x=1).select('xyz'):
raise Exception("mocked successfully")
I'd like to mock the foo.models.Bar.objects.filter(x=1).select('xyz').
Attempt 1
I've tried several approaches gleaned from various questions, notably using a decorator:
#mock.patch('foo.models.Bar.objects.filter.select')
def test_update(self, mock_select):
mock_select.return_value = [None]
X().update()
I never hit the inside of the mocked call, however- the test should fail due to the exception being raised.
Attempt 2
#mock.patch('foo.models.Bar')
def test_update(self, mock_Bar):
mock_Bar.objects.filter(x=1).select('xyz').return_value = [None]
X().update()
Attempt 3
#mock.patch('foo.models.Bar')
def test_update(self, mock_Bar):
mock_Bar.objects.filter().select().return_value = [None]
X().update()
Attempt 4
I then tried something more basic, to see if I could get an NPE, which didn't work either.
#mock.patch('foo.models.Bar')
def test_update(self, mock_Bar):
mock_Bar.return_value = None
X().update()
All of my attempts pass the test, instead of the exception firing like I expect it to.
It's late so I assume I must be overlooking something basic in the examples I've seen!?
I was able to have it pass by mocking objects. Attempt #3 is close, you just need to change it to filter.return_value.select.return_value to have it pass. Here's my suggestion though as it seems mocking .objects is the preferred way.
#mock.patch('foo.models.Bar.objects')
def test_update(self, mock_bar_objects):
mock_bar_objects.filter.return_value.select.return_value = [None]
X().update()
EDIT: Test run output:
ERROR: test_update (test_x.TestDjango)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/wholevinski/.virtualenvs/p2/lib/python2.7/site-packages/mock/mock.py", line 1305, in patched
return func(*args, **keywargs)
File "/home/wholevinski/so_test/django_mock/test/test_x.py", line 10, in test_update
X().update()
File "/home/wholevinski/so_test/django_mock/foo/x_module.py", line 6, in update
raise Exception("mocked successfully")
Exception: mocked successfully
----------------------------------------------------------------------
Ran 1 test in 0.002s
FAILED (errors=1)

How do I mock a class's function's return value?

I have a method in Python that looks like this (in comicfile.py):
from zipfile import ZipFile
...
class ComicFile():
...
def page_count(self):
"""Return the number of pages in the file."""
if self.file == None:
raise ComicFile.FileNoneError()
if not os.path.isfile(self.file):
raise ComicFile.FileNotFoundError()
with ZipFile(self.file) as zip:
members = zip.namelist()
pruned = self.prune_dirs(members)
length = len(pruned)
return length
I'm trying to write a unit test for this (I've already tested prune_dirs), and so for this is what I have (test_comicfile.py):
import unittest
import unittest.mock
import comicfile
...
class TestPageCount(unittest.TestCase):
def setUp(self):
self.comic_file = comicfile.ComicFile()
#unittest.mock.patch('comicfile.ZipFile')
def test_page_count(self, mock_zip_file):
# Store as tuples to use as dictionary keys.
members_dict = {('dir/', 'dir/file1', 'dir/file2'):2,
('file1.jpg', 'file2.jpg', 'file3.jpg'):3
}
# Make the file point to something to prevent FileNoneError.
self.comic_file.file = __file__
for file_tuple, count in members_dict.items():
mock_zip_file.return_value.namelist = list(file_tuple)
self.assertEqual(count, self.comic_file.page_count())
When I run this test, I get the following:
F..ss....
======================================================================
FAIL: test_page_count (test_comicfile.TestPageCount)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/usr/local/Cellar/python3/3.5.1/Frameworks/Python.framework/Versions/3.5/lib/python3.5/unittest/mock.py", line 1157, in patched
return func(*args, **keywargs)
File "/Users/chuck/Dropbox/Projects/chiv/chiv.cbstar/test_comicfile.py", line 86, in test_page_count
self.assertEqual(count, self.comic_file.page_count())
AssertionError: 2 != 0
----------------------------------------------------------------------
Ran 9 tests in 0.010s
FAILED (failures=1, skipped=2)
OK, so self.comic_file.page_count() is returning 0. I tried placing the following line after members = zip.namelist() in page_count.
print('\nmembers -> ' + str(members))
During the test, I get this:
members -> <MagicMock name='ZipFile().__enter__().namelist()' id='4483358280'>
I'm quite new to unit testing and am quite nebulous on using unittest.mock, but my understanding is that mock_zip-file.return_value.namelist = list(file_tuple) should have made it so that the namelist method of the ZipFile class would return each of the file_tuple contents in turn. What it is doing I have no idea.
I think what I'm trying to do here is clear, but I can't seem to figure out how to override the namelist method so that my unit test is only testing this one function instead of having to deal with ZipFile as well.
ZipFile is instantiated as a context manager. to mock it you have to refer to its __enter__ method.
mock_zip_file.return_value.__enter__.return_value.namelist.return_value = list(file_tuple)
What you're trying to do is very clear, but the context manager adds complexity to the mocking.
One trick is that when a mock registers all calls made to it, in this example it is saying it has a call at:
members -> <MagicMock name='ZipFile().__enter__().namelist()' id='4483358280'>
This can guide you in registering your mocked object, replace all () with return_value

Can I patch a static method in python?

I've a class in python that contains a static method. I want to mock.patch it in order to see if it was called. When trying to do it I get an error:
AttributeError: path.to.A does not have the attribute 'foo'
My setup can be simplified to:
class A:
#staticMethod
def foo():
bla bla
Now the test code that fails with error:
def test():
with mock.patch.object("A", "foo") as mock_helper:
mock_helper.return_value = ""
A.some_other_static_function_that_could_call_foo()
assert mock_helper.call_count == 1
You can always use patch as a decorator, my preferred way of patching things:
from mock import patch
#patch('absolute.path.to.class.A.foo')
def test(mock_foo):
mock_foo.return_value = ''
# ... continue with test here
EDIT: Your error seems to hint that you have a problem elsewhere in your code. Possibly some signal or trigger that requires this method that is failing?
I was getting that same error message when trying to patch a method using the #patch decorator.
Here is the full error I got.
Traceback (most recent call last):
File "/usr/local/lib/python3.6/site-packages/tornado/testing.py", line 136, in __call__
result = self.orig_method(*args, **kwargs)
File "/usr/local/lib/python3.6/unittest/mock.py", line 1171, in patched
arg = patching.__enter__()
File "/usr/local/lib/python3.6/unittest/mock.py", line 1243, in __enter__
original, local = self.get_original()
File "/usr/local/lib/python3.6/unittest/mock.py", line 1217, in get_original
"%s does not have the attribute %r" % (target, name)
AttributeError: <module 'py-repo.models.Device' from
'/usr/share/projects/py-repo/models/Device.py'> does not have the attribute 'get_device_from_db'
What I ended up doing to fix this was changing the patch decorator I used
from
#patch('py-repo.models.Device.get_device_from_db')
to #patch.object(DeviceModel, 'get_device_from_db')
I really wish I could explain further why that was the issue but I'm still pretty new to Python myself. The patch documentation was especially helpful in figuring out what was available to work with. Important: I should note that get_device_from_db uses the #staticmethod decorator which may be changing things. Hope it helps though.
What worked for me:
#patch.object(RedisXComBackend, '_handle_conn')
def test_xcoms(self, mock_method: MagicMock):
mock_method.return_value = fakeredis.FakeStrictRedis()
'_handle_conn' (static function) looks like this:
#staticmethod
def _handle_conn():
redis_hook = RedisHook()
conn: Redis = redis_hook.get_conn()

#mock.patch isn't raising an attribute error even after setting side_effect

I'm attempting to fix a bug in the python package caniusepython3 which arises because distlib isn't parsing pypi projects correctly. I've written this unit test
#mock.patch('distlib.locators.locate')
def test_blocking_dependencies_locators_fails(self, distlib_mock):
"""
Testing the work around for //bitbucket.org/pypa/distlib/issue/59/
"""
py3 = {'py3_project': ''}
breaking_project = 'test_project'
distlib_mock.locators.locate.return_value = "foo"
distlib_mock.locators.locate.side_effect = AttributeError()
got = dependencies.blocking_dependencies([breaking_project], py3)
# If you'd like to test that a message is logged we can use
# testfixtures.LogCapture or stdout redirects.
So that when distlib fixes the error in the next release of distlib the test case will still be valid.
The problem is that the MagicMock never raises a AttributeError as I expected and instead returns a string representation of the magic mock object
try:
# sets dist to <MagicMock name='locate()' id='4447530792'>
dist = distlib.locators.locate(project)
except AttributeError:
# This is a work around //bitbucket.org/pypa/distlib/issue/59/
log.warning('{0} found but had to be skipped.'.format(project))
continue
And causes this stack trace later on because it returns the object repr,
======================================================================
ERROR: Testing the work around for //bitbucket.org/pypa/distlib/issue/59/
----------------------------------------------------------------------
Traceback (most recent call last):
File "/usr/local/Cellar/python3/3.4.2_1/Frameworks/Python.framework/Versions/3.4/lib/python3.4/unittest/mock.py", line 1136, in patched
return func(*args, **keywargs)
File "/Users/alexlord/git/caniusepython3/caniusepython3/test/test_dependencies.py", line 81, in test_blocking_dependencies_locators_fails
got = dependencies.blocking_dependencies([breaking_project], py3)
File "/Users/alexlord/git/caniusepython3/caniusepython3/dependencies.py", line 119, in blocking_dependencies
return reasons_to_paths(reasons)
File "/Users/alexlord/git/caniusepython3/caniusepython3/dependencies.py", line 43, in reasons_to_paths
parent = reasons[blocker]
File "/Users/alexlord/git/caniusepython3/caniusepython3/dependencies.py", line 29, in __getitem__
return super(LowerDict, self).__getitem__(key.lower())
nose.proxy.KeyError: <MagicMock name='locate().name.lower().lower()' id='4345929400'>
-------------------- >> begin captured logging << --------------------
ciu: INFO: Checking top-level project: test_project ...
ciu: INFO: Locating <MagicMock name='locate().name.lower()' id='4344734944'>
ciu: INFO: Dependencies of <MagicMock name='locate().name.lower()' id='4344734944'>: []
--------------------- >> end captured logging << ---------------------
Why is the MagicMock not returning an exception when distlib.locator.locate() is called?
Update: I was able to get this unit test to work when I switched to using
def test_blocking_dependencies_locators_fails(self):
"""
Testing the work around for //bitbucket.org/pypa/distlib/issue/59/
"""
with mock.patch.object(distlib.locators, 'locate') as locate_mock:
py3 = {'py3_project': ''}
breaking_project = 'test_project'
locate_mock.side_effect = AttributeError()
got = dependencies.blocking_dependencies([breaking_project], py3)
# If you'd like to test that a message is logged we can use
# testfixtures.LogCapture or stdout redirects.
But I'm still wondering what I did wrong with the decorator format.
When you use #mock.patch, it mocks what you tell it, and passes that mock object as a parameter. Thus, your distlib_mock parameter is the mock locate function. You're effectively setting attributes on distlib.locators.locate.locators.locate. Set the attributes directly on the provided mock, and things should work better.
#mock.patch('distlib.locators.locate')
def test_blocking_dependencies_locators_fails(self, locate_mock):
# ...
locate_mock.return_value = "foo"
locate_mock.side_effect = AttributeError()
# ...

Categories