Prevent custom assert from showing in traceback of python unittest - python-3.x

I'd like to add some custom assert methods to a TestCase. As a simple example, I've just put one inside the test class below. It works as expected, but when the output is generated the traceback includes the custom assert in the output.
What is the step necessary to make it behave like assertEqual()? The code for assertEqual is in TestCase, but the actual line that raises the assertion does not appear in the traceback. What do I need to do to make test_something2's output look more like test_something1's?
import unittest
class TestCustomAssert(unittest.TestCase):
def assertSomething(self, s):
self.assertEqual(s, 'something')
def test_something1(self):
self.assertEqual('foo', 'something')
def test_something2(self):
self.assertSomething('foo')
if __name__ == '__main__':
unittest.main()
Output
python3 custom_assert.py
FF
======================================================================
FAIL: test_something1 (__main__.TestCustomAssert)
----------------------------------------------------------------------
Traceback (most recent call last):
File "custom_assert.py", line 8, in test_something1
self.assertEqual('foo', 'something')
AssertionError: 'foo' != 'something'
- foo
+ something
======================================================================
FAIL: test_something2 (__main__.TestCustomAssert)
----------------------------------------------------------------------
Traceback (most recent call last):
File "custom_assert.py", line 10, in test_something2
self.assertSomething('foo')
File "custom_assert.py", line 6, in assertSomething
self.assertEqual(s, 'something')
AssertionError: 'foo' != 'something'
- foo
+ something
----------------------------------------------------------------------
Ran 2 tests in 0.000s
FAILED (failures=2)

unittest doesn't print tracebacks from frames that have __unittest=True in their globals.
From unittest.result:
def _is_relevant_tb_level(self, tb):
return '__unittest' in tb.tb_frame.f_globals
So, if you make a helper module, you can emulate that behavior:
helper.py:
__unittest = True
def assert_stuff(s):
assert s == 'something', "%s is not something" % s
Now you can call this helper from your test case.
I guess that one could make a very neat decorator that make such magic more automatic without a helper module but IMHO you shouldn't make effort to reduce the traceback anyway.

Related

unittest #patch return first tuple in list of the tuple

I have a problem with unittest. The patched method does not return a list of tuples, but only one tuple:
My code
from date import Date
from ditreport import DIT_report
from unittest import TestCase
from unittest.mock import patch, Mock
def mock_get_uuid_messages_with_tmpls():
result = [('43e89d3a-af91-465f-a2db-3147988d1168',), ('93963bf6-9f75-4ffe-80e6-745307ef0a10',),
('88e68d67-8969-4059-9f6c-ff161879eb38',), ('39191cbd-79bb-483a-8df7-04aaf72253f1',),
('44a685c4-fb12-4431-ae18-3fb220e4d3e7',), ('3eef8189-7509-4dc1-9d71-c04f1cfc0d88',),
('4736beae-aa55-4bb3-b41d-3f94b1b178d1',), ('260db4a6-aab8-4d34-b293-cbf5fe6c7400',),
('7b73dfe0-5b8a-4a63-8607-43827eeac4c0',), ('fb51668f-0d2f-4958-911d-07d57a73fe56',)]
return result
class TestDIT_report(TestCase):
def setUp(self):
self.date_start='2020-01-12'
self.date_end = '2020-02-01'
self.configfile='config/config.def.xml'
self.param='covid'
self.report = DIT_report(self.date_start, self.date_end, self.configfile)
#patch('ditreport.DIT_report.get_uuid_messages_with_tmpls', side_effect=mock_get_uuid_messages_with_tmpls())
def test_get_uuid_messages_with_tmpls(self, get_uuid_messages_with_tmpls):
messages_uuid = get_uuid_messages_with_tmpls()
self.assertEqual(10,len(messages_uuid))
messages_uuid should get a list of ten tuples but get the first tuple
Launching unittests with arguments python -m unittest
TestDIT_report.TestDIT_report.test_get_uuid_messages_with_tmpls in
/home/skif/PycharmProjects/reports
init config
Ran 1 test in 0.026s
FAILED (failures=1)
1 != 10
Expected :10 Actual :1
Traceback (most recent call last): File
"/home/skif/pycharm-2020/plugins/python/helpers/pycharm/teamcity/diff_tools.py",
line 32, in _patched_equals
old(self, first, second, msg) File "/usr/lib/python3.8/unittest/case.py", line 912, in assertEqual
assertion_func(first, second, msg=msg) File "/usr/lib/python3.8/unittest/case.py", line 905, in _baseAssertEqual
raise self.failureException(msg) AssertionError: 10 != 1
During handling of the above exception, another exception occurred:
Traceback (most recent call last): File
"/usr/lib/python3.8/unittest/case.py", line 60, in testPartExecutor
yield File "/usr/lib/python3.8/unittest/case.py", line 676, in run
self._callTestMethod(testMethod) File "/usr/lib/python3.8/unittest/case.py", line 633, in _callTestMethod
method() File "/usr/lib/python3.8/unittest/mock.py", line 1325, in patched
return func(*newargs, **newkeywargs) File "/home/skif/PycharmProjects/reports/TestDIT_report.py", line 86, in
test_get_uuid_messages_with_tmpls
self.assertEqual(10,len(messages_uuid))
Why do I get this error? Maybe I missed what parameter? But I have a similar code and it returns a normal list of tuples.
Its a simple mistake.
#patch('ditreport.DIT_report.get_uuid_messages_with_tmpls',
side_effect=mock_get_uuid_messages_with_tmpls())
should be
#patch('ditreport.DIT_report.get_uuid_messages_with_tmpls',
side_effect=mock_get_uuid_messages_with_tmpls)

Python cmd shell: handling undefined commands

I'm writing a shell that must be able to take an unlimited number of commands. I can't figure out from the docs (https://docs.python.org/3.6/library/cmd.html) which say:
Cmd.default(line) Method called on an input line when the command
prefix is not recognized. If this method is not overridden, it prints
an error message and returns.
I must be writing the default() method incorrectly?
I've tried this:
import cmd
class MyShell(cmd.Cmd):
def default():
print('you entered this unknown command: ')
if __name__ == '__main__':
MyShell().cmdloop()
but get this (when I enter 'hi' in the shell):
(Cmd) hi
Traceback (most recent call last):
File "/Users/david/anaconda/lib/python3.6/cmd.py", line 214, in onecmd
func = getattr(self, 'do_' + cmd)
AttributeError: 'MyShell' object has no attribute 'do_hi'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "bitmessage_shell.py", line 9, in <module>
MyShell().cmdloop()
File "/Users/david/anaconda/lib/python3.6/cmd.py", line 138, in cmdloop
stop = self.onecmd(line)
File "/Users/david/anaconda/lib/python3.6/cmd.py", line 216, in onecmd
return self.default(line)
TypeError: default() takes 0 positional arguments but 2 were given
def default():
print('you entered this unknown command: ')
that doesn't work in a class. You need at least the object argument (self), or make the method static/class using #staticmethod or #classmethod decorators (but not very convenient, you may need the object state at some point)
Moreover, the parent class seems to pass the line, so now you need 2 arguments:
def default(self,line):
print('you entered this unknown command: ',line)

Why does 'yield' in a test case let the test pass

I am trying to understand the following python code snippet, taken from the SublimeText3 plugin development unit testing examples.
def test_delayed_insert(self):
sublime.set_timeout(
lambda: self.view.run_command("delayed_insert_hello_world"),
100)
# `delayed_insert_hello_world` will be execulated after the timeout
# `yield 1000` will yield the runtime to main thread and continue
# the execution 1 second later
yield 1000
row = self.getRow(0)
self.assertEqual(row, "hello world")
How can this work? If unittest does not support this (call the test as a generator), the code would not be executed, right?
My current understanding is the unittest framework takes the yielded value 'x', and suspends itself for 'x' ms, in this case 1000ms, similar to the semantics of yield/sleep in c++ threads. However, that depends on 'unittest' to really use the yielded value in this way.
If it does, why does this test not fail (tried it within st3 = python 3.3.6 and python 3.5.2) ?
from unittest import TestCase
class TestMe(TestCase):
def test_failtest(self):
self.assertTrue(False)
yield 0
Running it gives:
~$ python3 -m unittest test_me.py --verbose
test_failtest (test_me.TestMe) ... ok
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
TL;DR the code you linked to is subclassing a custom TestCase class (DeferrableTestCase) which may alter the "normal" behavior of unittest.
Let's take a step back and not use unittest.
def foo():
assert True is False
foo()
This raises an AssertionError as expected. However,
def foo():
assert True is False
yield
foo()
Does not.
The reason is that the yield keyword turns foo to a generator.
In order to get the AssertionError we would have to consume the generator that foo returns:
def foo():
assert True is False
yield
next(foo())
File "main.py", line 48, in <module>
next(foo())
File "main.py", line 45, in foo
assert True is False
AssertionError
This behavior is inherit to generators and not related to how unittest or assert work:
def foo():
1/0
yield
gen = foo()
print('No exception yet')
next(gen)
Outputs
'No exception yet'
Traceback (most recent call last):
File "main.py", line 50, in <module>
next(gen)
File "main.py", line 45, in foo
1/0
ZeroDivisionError: division by zero
However, the code you linked to is subclassing a custom TestCase class (DeferrableTestCase) which may alter the behavior.

How to manupulate Python unittest output '.' , E and F

Python unittest give '.', E or F on ok, error or fail. We can avoid it by setting verbosity = 0, or /dev/null. but how can we change it. I mean I want to write after every test PASS, FAIL or ERROR instead of ., E or F, without using verbosity flag -V for output.
I am looking for following kind of output:
test one
PASS
test two
FAIL
test three
PASS
test four
ERROR
Thanks
Zubair
So using extra verbosity (-v) on the command line gets the desired output format:
Passing the -v option to your test script will instruct unittest.main()to enable a higher level of verbosity, and produce the following output:
test_isupper (__main__.TestStringMethods) ... ok
test_split (__main__.TestStringMethods) ... ok
test_upper (__main__.TestStringMethods) ... ok
----------------------------------------------------------------------
Ran 3 tests in 0.001s
OK
(https://docs.python.org/3/library/unittest.html#basic-example)
But we can also set the verbosity level from code:
You can run tests with more detailed information by passing in the verbosity argument:
if __name__ == '__main__':
unittest.main(verbosity=2)
(https://docs.python.org/3/library/unittest.html#unittest.main)
You can define your own test runner and result class to customize almost everything. If you just want to change the output of the test names (just the method name without the class) and change ok to PASS it's enough to override the startTest and addSuccess methods of the standard TextTestResult:
import unittest
class Test(unittest.TestCase):
def test_pass(self):
self.assertTrue(True)
def test_fail(self):
self.assertTrue(False)
def test_error(self):
x = 1 / 0
class MyTestResult(unittest.TextTestResult):
def __init__(self, stream, descriptions, verbosity):
super().__init__(stream, descriptions, verbosity)
self.stream = stream
self.verbosity = verbosity
def addSuccess(self, test):
if self.verbosity > 1:
self.stream.writeln("PASS")
else:
super().addSuccess(test)
def startTest(self, test):
unittest.TestResult.startTest(self, test)
if self.verbosity > 1:
self.stream.write(f"{test._testMethodName} ")
self.stream.flush()
if __name__ == "__main__":
unittest.main(testRunner=unittest.TextTestRunner(resultclass=MyTestResult, verbosity=2))
Output:
test_error ERROR
test_fail FAIL
test_pass PASS
======================================================================
ERROR: test_error (__main__.Test)
----------------------------------------------------------------------
Traceback (most recent call last):
File "test.py", line 12, in test_error
x = 1 / 0
ZeroDivisionError: division by zero
======================================================================
FAIL: test_fail (__main__.Test)
----------------------------------------------------------------------
Traceback (most recent call last):
File "test.py", line 9, in test_fail
self.assertTrue(False)
AssertionError: False is not true
----------------------------------------------------------------------
Ran 3 tests in 0.001s
FAILED (failures=1, errors=1)
instead of the standard output:
test_error (__main__.Test) ... ERROR
test_fail (__main__.Test) ... FAIL
test_pass (__main__.Test) ... ok
...

How can I catch an error without using try/except?

Is there something that I could use to catch errors in python without using a try/except?
I'm thinking of something like this:
main.py
from catch_errors import catch_NameError
print(this_variable_is_not_defined)
catch_errors.py
def catch_NameError(error):
if type(error) == NameError:
print("You didn't define the error")
The output would be:
You didn't define the error
Instead of:
Traceback (most recent call last):
File "main.py", line 1, in <module>
print(this_variable_is_not_defined)
NameError: name 'this_variable_is_not_defined' is not defined
It can be done by creating a context manager, but it gives questionable benefit over an explicit try:except:. You will have to use the with statement, so it will be clear where behavior will change. In this example, I am using contextlib.contextmanager to do this, which saves the tedium of creating a class with __enter__ and __exit__ methods.
from contextlib import contextmanager
#contextmanager
def IgnoreNameErrorExceptions():
"""Context manager to ignore NameErrors."""
try:
yield
except NameError as e:
print(e) # You can print whatever you want here.
with IgnoreNameErrorExceptions():
print(this_variable_is_not_defined)
This will output
name 'this_variable_is_not_defined' is not defined

Resources