I want to add a custom assert method to a TestCase subclass. I tried to copy my implementation from the unittest module so that it would match the behaviour of the regular TestCase as closely as possible. (I would prefer to just delegate to self.assertEqual() but this causes even more backtrace noise, see below.) The unittest module seems to automatically hide some internal details of its implementation when reporting failed assertions.
import unittest
class MyTestCase(unittest.TestCase):
def assertLengthIsOne(self, sequence, msg=None):
if len(sequence) != 1:
msg = self._formatMessage(msg, "length is not one")
raise self.failureException(msg)
class TestFoo(MyTestCase):
seq = (1, 2, 3, 4, 5)
def test_stock_unittest_assertion(self):
self.assertEqual(len(self.seq), 1)
def test_custom_assertion(self):
self.assertLengthIsOne(self.seq)
unittest.main()
The output of this is as such:
amoe#vuurvlieg $ python unittest-demo.py
FF
======================================================================
FAIL: test_custom_assertion (__main__.TestFoo)
----------------------------------------------------------------------
Traceback (most recent call last):
File "unittest-demo.py", line 16, in test_custom_assertion
self.assertLengthIsOne(self.seq)
File "unittest-demo.py", line 7, in assertLengthIsOne
raise self.failureException(msg)
AssertionError: length is not one
======================================================================
FAIL: test_stock_unittest_assertion (__main__.TestFoo)
----------------------------------------------------------------------
Traceback (most recent call last):
File "unittest-demo.py", line 13, in test_stock_unittest_assertion
self.assertEqual(len(self.seq), 1)
AssertionError: 5 != 1
----------------------------------------------------------------------
Ran 2 tests in 0.000s
FAILED (failures=2)
Note that the custom assert method causes a stack trace with two frames, one inside the method itself, whereas the stock unittest method only has one frame, the relevant line in the user's code. How can I apply this frame-hiding behaviour to my own method?
This question was answered by Peter Otten on comp.lang.python.
Move MyTestCase in a separate module and define a global variable __unittest = True.
$ cat mytestcase.py
import unittest
__unittest = True
class MyTestCase(unittest.TestCase):
def assertLengthIsOne(self, sequence, msg=None):
if len(sequence) != 1:
msg = self._formatMessage(msg, "length is not one")
raise self.failureException(msg)
$ cat mytestcase_demo.py
import unittest
from mytestcase import MyTestCase
class TestFoo(MyTestCase):
seq = (1, 2, 3, 4, 5)
def test_stock_unittest_assertion(self):
self.assertEqual(len(self.seq), 1)
def test_custom_assertion(self):
self.assertLengthIsOne(self.seq)
if __name__ == "__main__":
unittest.main()
$ python mytestcase_demo.py
FF
======================================================================
FAIL: test_custom_assertion (__main__.TestFoo)
----------------------------------------------------------------------
Traceback (most recent call last):
File "mytestcase_demo.py", line 11, in test_custom_assertion
self.assertLengthIsOne(self.seq)
AssertionError: length is not one
======================================================================
FAIL: test_stock_unittest_assertion (__main__.TestFoo)
----------------------------------------------------------------------
Traceback (most recent call last):
File "mytestcase_demo.py", line 8, in test_stock_unittest_assertion
self.assertEqual(len(self.seq), 1)
AssertionError: 5 != 1
----------------------------------------------------------------------
Ran 2 tests in 0.000s
FAILED (failures=2)
$
Related
I am trying to take a UML class diagram and implement it.
The following code is for testing my resulting Die Class implementation.
import unittest
from Die import Die
class DieTest(unittest.TestCase):
def test_instantiation(self):
d6 = Die()
self.assertEqual(d6.getSides(), 6)
self.assertFalse(d6.getSides() == 3)
d3 = Die(3)
self.assertTrue(d3.getSides() == 3)
self.assertFalse(d3.getSides() == 6)
self.assertFalse(d6.replaceDie())
def test_rolls(self):
d6 = Die()
self.assertTrue(d6.rollDie() < 7)
self.assertFalse(d6.rollDie() > 6)
self.assertTrue(d6.rollTwoDie() < 13)
self.assertTrue(d6.rollTwoDie() <= 12)
self.assertTrue(d6.rollMultiDie(3) > 2)
self.assertTrue(d6.rollMultiDie(4) > 4)
def test_count(self):
d1 = Die()
d2 = Die()
d3 = Die()
d4 = Die()
self.assertTrue(d1.getDieCount() == 4)
def test_replace(self):
d1 = Die()
self.assertFalse(d1.replaceDie())
for x in range(1, 110):
d1.rollDie()
self.assertTrue(d1.replaceDie())
def test_totalRolls(self):
d1 = Die()
for x in range(1, 110):
d1.rollDie()
self.assertTrue(d1.getIncrementRolls() == 109)
d1.resetIncrementRolls()
self.assertTrue(d1.getIncrementRolls() == 0)
if __name__ == "__main__":
unittest.main()
So, what I have come up with is this -
import random
class Die:
def __init__(self, n=6) -> None:
self.numSides = n
self.numDie = 0
self.totalRolls = 0
self.replaceDie = False
pass
def rollDie(self):
return random.randrange(1, self.numSides)
def rollTwoDie(self):
return self.rollDie()+self.rollDie()
def rollMultiDie(self, n=2):
sum = 0
for i in range(n):
sum += self.rollDie()
def getSides(self):
return self.numSides
def getDieCount(self):
return self.numDie
def incrementRolls(self):
self.totalRolls = self.totalRolls + 1
if(self.totalRolls > 100):
self.replaceDie = True
def getIncrementRolls(self):
return self.incrementRolls
def resetIncrementRolls(self):
self.incrementRolls = 0
def replaceDie(self):
return self.replaceDie
My results -
FEEEF
====================================================================== ERROR: test_instantiation (main.DieTest)
---------------------------------------------------------------------- Traceback (most recent call last): File
"---------------------
5 attached files Mar 13, 2022 943 PM/DieTest.py", line 20, in
test_instantiation
self.assertFalse(d6.replaceDie()) TypeError: 'bool' object is not callable
====================================================================== ERROR: test_replace (main.DieTest)
---------------------------------------------------------------------- Traceback (most recent call last): File
"---------------------
5 attached files Mar 13, 2022 943 PM/DieTest.py", line 40, in
test_replace
self.assertFalse(d1.replaceDie()) TypeError: 'bool' object is not callable
====================================================================== ERROR: test_rolls (main.DieTest)
---------------------------------------------------------------------- Traceback (most recent call last): File
"---------------------
5 attached files Mar 13, 2022 943 PM/DieTest.py", line 28, in
test_rolls
self.assertTrue(d6.rollMultiDie(3) > 2) TypeError: '>' not supported between instances of 'NoneType' and 'int'
====================================================================== FAIL: test_count (main.DieTest)
---------------------------------------------------------------------- Traceback (most recent call last): File
"---------------------
5 attached files Mar 13, 2022 943 PM/DieTest.py", line 36, in
test_count
self.assertTrue(d1.getDieCount() == 4) AssertionError: False is not true
====================================================================== FAIL: test_totalRolls (main.DieTest)
---------------------------------------------------------------------- Traceback (most recent call last): File
"---------------------
5 attached files Mar 13, 2022 943 PM/DieTest.py", line 49, in
test_totalRolls
self.assertTrue(d1.getIncrementRolls() == 109) AssertionError: False is not true
---------------------------------------------------------------------- Ran 5 tests in 0.004s
FAILED (failures=2, errors=3)
With out changing any code in DieTest.py, what am I doing wrong with my class implementation?
I have a python module containing (amongst other functions) this piece of code:
def check_color_range(*argv):
"""
Abbreviated documentation and other tests.
>>> check_color_range(23, -1, 99, 10000)
Traceback (most recent call last):
...
TypeError: Falscher Farbwert -1!
"""
for c in argv:
if c < 0 or c > 255:
raise TypeError('Falscher Farbwert ' + str(c) + '!')
When I run this using doctest like so: python -m doctest -v demo.py, I get the following output:
Trying:
check_color_range(23, -1, 99, 10000)
Expecting:
Traceback (most recent call last):
...
TypeError: Falscher Farbwert -1!
**********************************************************************
File "C:\az\code_camp_python\src\EigeneProgramme\Tag3\arcade_base\demo.py", line 5, in demo.check_color_range
Failed example:
check_color_range(23, -1, 99, 10000)
Expected:
Traceback (most recent call last):
...
TypeError: Falscher Farbwert -1!
Got:
Traceback (most recent call last):
File "C:\az\miniconda3\envs\py37\lib\doctest.py", line 1329, in __run
compileflags, 1), test.globs)
File "<doctest demo.check_color_range[0]>", line 1, in <module>
check_color_range(23, -1, 99, 10000)
File "C:\az\code_camp_python\src\EigeneProgramme\Tag3\arcade_base\demo.py", line 12, in check_color_range
raise TypeError('Falscher Farbwert ' + str(c) + '!')
TypeError: Falscher Farbwert -1!
1 items had no tests:
demo
**********************************************************************
1 items had failures:
1 of 1 in demo.check_color_range
1 tests in 2 items.
0 passed and 1 failed.
***Test Failed*** 1 failures.
For me the expected and the actual Errors look the same, but I may be missing something. I already compared whitespace etc., which seems to be the same.
I then tried to paste the complete Traceback from the "Got:" section into the testcase - and I'm still get the failed test, so I guess I must be doing something wrong.
I'd be very happy, if you could give me a heads-up.
On line 8 you have: TypeError: Falscher Farbwert -1!____ (4 blank spaces at the end)
You should replace it with: TypeError: Falscher Farbwert -1!
Does doctest support that both output and exception mixed together?
One example is:
>>> def foo():
... print 'hello world!'
>>> foo()
hello world!
>>> def bar():
... raise Exception()
>>> bar()
Traceback (most recent call last):
...
Exception
>>> def foo_bar():
... foo()
... bar()
>>> foo_bar()
hello world!
Traceback (most recent call last):
...
Exception
I expect all three cases should be successful, but only two of them does, see
$ python -m doctest -v /tmp/1.py
Trying:
def foo():
print 'hello world!'
Expecting nothing
ok
Trying:
foo()
Expecting:
hello world!
ok
Trying:
def bar():
raise Exception()
Expecting nothing
ok
Trying:
bar()
Expecting:
Traceback (most recent call last):
...
Exception
ok
Trying:
def foo_bar():
foo()
bar()
Expecting nothing
ok
Trying:
foo_bar()
Expecting:
hello world!
Traceback (most recent call last):
...
Exception
**********************************************************************
File "/tmp/1.py", line 16, in 1
Failed example:
foo_bar()
Exception raised:
Traceback (most recent call last):
File "/usr/lib/python2.7/doctest.py", line 1315, in __run
compileflags, 1) in test.globs
File "<doctest 1[5]>", line 1, in <module>
foo_bar()
File "<doctest 1[4]>", line 3, in foo_bar
bar()
File "<doctest 1[2]>", line 2, in bar
raise Exception()
Exception
**********************************************************************
1 items had failures:
1 of 6 in 1
6 tests in 1 items.
5 passed and 1 failed.
***Test Failed*** 1 failures.
The docs say you can't do that:
Examples containing both expected output and an exception are not supported. Trying to guess where one ends and the other begins is too error-prone, and that also makes for a confusing test.
Regular output and tracebacks cannot be mixed since they are just indistinguishable text. However you can wrap the block, catching the exception you expect:
>>> try:
... foo_bar()
... except TheSpecificExceptionYouWant:
... pass
... else:
... raise AssertionError('Should have raised an exception')
hello world!
I have this script
import unittest,itertools,random
##testclass
class Testcomb(unittest.TestCase):
def test_input(self):
self.assertRaises(TypeError,calculate_combinations,dict(comb1), 5)
def calculate_combinations(combin,target):
counter = 0
for L in range(0, len(combin)+1):
for subset in itertools.combinations(combin, L):
if sum(subset) == target: counter= counter+1
return counter
comb1=[1,2,3,4]
if __name__=='__main__': unittest.main()
but the self.assertRaises(TypeError,calculate_combinations,dict(comb1), 5) does not intercept the exception giving me this error:
E..
======================================================================
ERROR: test_input (__main__.Testcomb)
----------------------------------------------------------------------
Traceback (most recent call last):
File "total_combination.py", line 25, in test_input
self.assertRaises(TypeError,calculate_combinations,dict(comb1), 5)
TypeError: cannot convert dictionary update sequence element #0 to a sequence
----------------------------------------------------------------------
Ran 3 tests in 0.000s
FAILED (errors=1)
Can anyone help?
The exception that makes your test fail is triggered by the dict(comb1) part of the assertion.
>>> comb1=[1,2,3,4]
>>> dict(comb1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: cannot convert dictionary update sequence element #0 to a sequence
On the contrary, the assertRaises will return True only if it is the callable (in your case calculate_combinations) to trigger it.
HTH!
The following snippet:
import traceback
def a():
b()
def b():
try:
c()
except:
traceback.print_exc()
def c():
assert False
a()
Produces this output:
Traceback (most recent call last):
File "test.py", line 8, in b
c()
File "test.py", line 13, in c
assert False
AssertionError
What should I use if I want the complete stack trace including the call to a?
If it matters I have Python 2.6.6
edit: What I'd like to get is the same information I'd get if I left the try except out and let the exception propagate to the top level. This snippet for example:
def a():
b()
def b():
c()
def c():
assert False
a()
Produces this output:
Traceback (most recent call last):
File "test.py", line 10, in <module>
a()
File "test.py", line 2, in a
b()
File "test.py", line 5, in b
c()
File "test.py", line 8, in c
assert False
AssertionError
Here's a function based on this answer. It will also work when no exception is present:
def full_stack():
import traceback, sys
exc = sys.exc_info()[0]
stack = traceback.extract_stack()[:-1] # last one would be full_stack()
if exc is not None: # i.e. an exception is present
del stack[-1] # remove call of full_stack, the printed exception
# will contain the caught exception caller instead
trc = 'Traceback (most recent call last):\n'
stackstr = trc + ''.join(traceback.format_list(stack))
if exc is not None:
stackstr += ' ' + traceback.format_exc().lstrip(trc)
return stackstr
print full_stack() will print the full stack trace up to the top, including e.g. IPython's interactiveshell.py calls, since there is (to my knowledge) no way of knowing who would catch exceptions. It's probably not worth figuring out anyway...
If print full_stack() is called from within an except block, full_stack will include the stack trace down to the raise. In the standard Python interpreter, this will be identical to the message you receive when not catching the exception (Which is why that del stack[-1] is there, you don't care about the except block but about the try block).
I don't know if there is a better way, but here's what I did:
import traceback
import sys
def format_exception(e):
exception_list = traceback.format_stack()
exception_list = exception_list[:-2]
exception_list.extend(traceback.format_tb(sys.exc_info()[2]))
exception_list.extend(traceback.format_exception_only(sys.exc_info()[0], sys.exc_info()[1]))
exception_str = "Traceback (most recent call last):\n"
exception_str += "".join(exception_list)
# Removing the last \n
exception_str = exception_str[:-1]
return exception_str
def main1():
main2()
def main2():
try:
main3()
except Exception as e:
print "Printing only the traceback above the current stack frame"
print "".join(traceback.format_exception(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2]))
print
print "Printing the full traceback as if we had not caught it here..."
print format_exception(e)
def main3():
raise Exception()
if __name__ == '__main__':
main1()
And here's the output I get:
Printing only the traceback above the current stack frame
Traceback (most recent call last):
File "exc.py", line 22, in main2
main3()
File "exc.py", line 31, in main3
raise Exception()
Exception
Printing the full traceback as if we had not caught it here...
Traceback (most recent call last):
File "exc.py", line 34, in <module>
main1()
File "exc.py", line 18, in main1
main2()
File "exc.py", line 22, in main2
main3()
File "exc.py", line 31, in main3
raise Exception()
Exception
Use
traceback.print_stack()
http://docs.python.org/library/traceback.html#traceback.print_stack
suxmac2 $ python out.py
File "out.py", line 16, in <module>
a()
File "out.py", line 5, in a
b()
File "out.py", line 11, in b
traceback.print_stack()
Here is a bit better variant of Tobias Kienzler answer. It works same, but can be called not right in except block, but somewhere deeper.
In other words, this variant will print same stacks, when called like
try:
...
except Exception:
print full_stack()
or
def print_full_stack():
print full_stack()
try:
...
except Exception:
print_full_stack()
Here is code:
def full_stack():
import traceback, sys
exc = sys.exc_info()[0]
if exc is not None:
f = sys.exc_info()[-1].tb_frame.f_back
stack = traceback.extract_stack(f)
else:
stack = traceback.extract_stack()[:-1] # last one would be full_stack()
trc = 'Traceback (most recent call last):\n'
stackstr = trc + ''.join(traceback.format_list(stack))
if exc is not None:
stackstr += ' ' + traceback.format_exc().lstrip(trc)
return stackstr