473,387 Members | 1,691 Online
Bytes | Software Development & Data Engineering Community
Post Job

Home Posts Topics Members FAQ

Join Bytes to post your question to a community of 473,387 software developers and data experts.

Unittest - How do I code lots of simple tests

One of the things I really dislike about Unittest (compared, say, to a
number of adhoc testing tricks I've used in the past, and to Perl's
"standard" testing framework) is that the testcase-as-a-class model
tends to imply a relatively high granularity in testing.

A good example of this comes from "Dive Into Python"
(http://diveintopython.org) section 7.3. Here, the author has written
a module which converts numbers to Roman numerals. The test case is
then

class KnownValues(unittest.TestCase):
knownValues = ( (1, 'I'), ... # 50-plus specific cases

def testToRomanKnownValues(self):
"""toRoman should give known result with known input"""
for integer, numeral in self.knownValues:
result = roman.toRoman(integer)
self.assertEqual(numeral, result)

Now, to my mind, the big problem here is that this *isn't* one test,
but rather 50 (or more). OK, the same checks are done if everything
succeeds, but

1. If a test fails, the rest are skipped! If there's a pattern to the
failures (the code handles numbers ending in 4 and 9 wrongly, for
example) it's much easier to find if all of the checks are
reported.
2. Psychologically, "52 tests succeeded" is a much bigger boost than
"1 test succeeded" (albeit this test covered 52 cases). And it's
not just psychology - we really *did* test 52 distinct conditions.

The only way I can see of producing the "true" number of tests is via
some form of ugly hack like

test_values = ((1, 'I'), ...)

class KnownValues(unittest.TestCase):
pass

for arabic, roman in test_values:
def test(self):
result = roman.toRoman(arabic)
self.assertEqual(roman, result)
setattr(KnownValues, 'test_%s_%s' % (arabic, roman), test)

But I can't really see that as the "right approach".

Can anyone suggest a more reasonable way of running this sort of
table-driven test via unittest?

Maybe I should use doctest instead, but to be honest, I prefer the
overall infrastructure of unittest (for real tests). It's just this
particular issue that really bugs me...

Paul.
--
This signature intentionally left blank
Jul 18 '05 #1
12 2556
Paul Moore wrote:

Can anyone suggest a more reasonable way of running this sort of
table-driven test via unittest?


Why not just extend self.assertEqual() and use your own check, with
additional logic as required to increment counters or add items
to the list of passing tests. Then put a final check of the number
of passing tests or something like that at the end to make sure
things worked overall.
For example:

class KnownValues(unittest.TestCase):
def setUp(self):
self.passCount = 0

def checkValue(self, expected, result):
if expected == result:
self.passCount += 1
else:
# left as exercise to the reader, but pass would work...

def testToRomanKnownValues(self):
for integer, numeral in self.knownValues:
result = roman.toRoman(integer)
self.checkValue(numeral, result)
self.assertEqual(len(self.knownValues), self.passCount)

No, you don't get the psychologically affirming "52 tests passed!"
without changes to the TestRunner, but I assume the non-cosmetic part
of this is more your concern right now...

-Peter
Jul 18 '05 #2
Paul Moore wrote:
Can anyone suggest a more reasonable way of running this sort of
table-driven test via unittest?

Take a look at Docutils' test/DocutilsTestSupport.py for ideas. The
CustomTestSuite and CustomTestCase classes provide support for named
data-driven tests. Most of Docutils' tests are data-driven.

Ian Bicking wrote: unittest is not written with subclassing in mind, except for the
limited subclassing that is documented. (And it uses
double-underscore variables, like it's just *trying* to piss me off!
Double-underscore variables are so arrogant and patronizing.


All very true. Double-underscores ought to be banned from the
standard library. They inevitably get in the way because no matter
how well a class is written, somebody is going to want to subclass it
in a way the original author never considered.

--
David Goodger http://starship.python.net/~goodger
For hire: http://starship.python.net/~goodger/cv
Docutils: http://docutils.sourceforge.net/
(includes reStructuredText: http://docutils.sf.net/rst.html)
Jul 18 '05 #3
Paul Moore <pf******@yahoo.co.uk> wrote in message news:<br**********@yahoo.co.uk>...
1. If a test fails, the rest are skipped! If there's a pattern to the
failures (the code handles numbers ending in 4 and 9 wrongly, for
example) it's much easier to find if all of the checks are
reported.


That's true, but most of the time when I test, I prefer that behavior.
Many of my asserts depend on the success of the assert prior to
them, so I want the test to fail as soon as one of the asserts has
failed.

It'd be nice to have both possibilities, though.

Jeremy
Jul 18 '05 #4
Hello Paul,
test_values = ((1, 'I'), ...)

class KnownValues(unittest.TestCase):
pass

for arabic, roman in test_values:
def test(self):
result = roman.toRoman(arabic)
self.assertEqual(roman, result)
setattr(KnownValues, 'test_%s_%s' % (arabic, roman), test)

But I can't really see that as the "right approach".

On reason that it won't do what you want :-)
All the tests will check the last value in test_values. (Something to
do with binding rules, can't recall how to solve)

Try:
--- i2r.py ---
#!/usr/bin/env python
from unittest import TestCase, makeSuite, main

class Roman:
def toRoman(self, i):
return { 1 : "I",
2 : "II",
3 : "III",
4 : "IV",
5 : "V"}[i]
roman = Roman()

class KnownValues(TestCase):
pass

test_values = ((1, "I"), (2, "II"), (3, "III"), (4, "IV"), (5, "V"))
for a, r in test_values:
def test(self):
print a, r
result = roman.toRoman(a)
self.assertEqual(r, result)
setattr(KnownValues, "test_%s_%s" % (a, r), test)

test_suite = makeSuite(KnownValues, "test_")

if __name__ == "__main__":
main()
--- i2r.py ---

$ python i2r.py
5 V
..5 V
..5 V
..5 V
..5 V
..
----------------------------------------------------------------------
Ran 5 tests in 0.000s

OK
HTH.
Miki
Jul 18 '05 #5
Paul Moore <pf******@yahoo.co.uk> wrote in news:br**********@yahoo.co.uk:
But I can't really see that as the "right approach".

Can anyone suggest a more reasonable way of running this sort of
table-driven test via unittest?


Ok, how about the file below.
It uses a metaclass to generate dynamic tests from a table. I deliberately
wrote the tests so that one fails, when run it will tell you that
test_roman_v failed so you can see that the names get handled properly.

Output:
D:\temp>tabletest.py --verbose
testMe (__main__.MyTests) ... ok
test_roman_i (__main__.MyTests) ... ok
test_roman_v (__main__.MyTests) ... FAIL
test_roman_x (__main__.MyTests) ... ok

================================================== ====================
FAIL: test_roman_v (__main__.MyTests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "D:\temp\tabletest.py", line 21, in test
self.assertEquals(roman, 'x')
File "D:\Python23\lib\unittest.py", line 302, in failUnlessEqual
raise self.failureException, \
AssertionError: 'v' != 'x'

----------------------------------------------------------------------
Ran 4 tests in 0.020s

FAILED (failures=1)
Note that the metaclass itself is reusable. The factory
function tableDrivenTests, although defined within the class is a function,
not a method, and cannot access any members of the class (since the class
does not yet exist at the time it is called). The table itself either has
to be created inside the tableDrivenTests function, or global.
The factory function simply returns a dictionary of functions which are
added to the class, note that the keyname in the dictionary is important so
far as the unittest code is concerned, not the original name of the
function.

Also be sure to pass parameters into the test function using default
parameters as nested scopes will get the values left at the end of the loop
(so you might end up with lots of tests that all do the same thing). I
mention that here because I did exactly that writing the code.

---- begin tabletest.py ----
class MetaTableTest(type):
def __new__(metacls, name, bases, dict):
factory = dict['tableDrivenTests']
dict.update(factory())
return super(MetaTableTest, metacls).__new__(metacls, name, bases,
dict)

import unittest

class MyTests(unittest.TestCase):
__metaclass__ = MetaTableTest
def tableDrivenTests():
'''Return a dictionary of additional test functions'''
knownValues = (1,'i'), (5, 'v'), (10, 'x')
table = {}
for arabic, roman in knownValues:
def test(self, arabic=arabic, roman=roman):
if arabic==1:
self.assertEquals(roman, 'i')
else:
self.assertEquals(roman, 'x')

table['test_roman_%s' % roman] = test
return table

def testMe(self):
self.assert_(True)
if __name__=='__main__':
unittest.main()

---- end tabletest.py ----

--
Duncan Booth du****@rcp.co.uk
int month(char *p){return(124864/((p[0]+p[1]-p[2]&0x1f)+1)%12)["\5\x8\3"
"\6\7\xb\1\x9\xa\2\0\4"];} // Who said my code was obscure?
Jul 18 '05 #6
Miki Tebeka wrote:
Hello Paul,
test_values = ((1, 'I'), ...)

class KnownValues(unittest.TestCase):
pass

for arabic, roman in test_values:
def test(self):
result = roman.toRoman(arabic)
self.assertEqual(roman, result)
setattr(KnownValues, 'test_%s_%s' % (arabic, roman), test)

But I can't really see that as the "right approach". On reason that it won't do what you want :-)
All the tests will check the last value in test_values. (Something to
do with binding rules, can't recall how to solve)

Try:
--- i2r.py ---
#!/usr/bin/env python
from unittest import TestCase, makeSuite, main

class Roman:
def toRoman(self, i):
return { 1 : "I",
2 : "II",
3 : "III",
4 : "IV",
5 : "V"}[i]
roman = Roman()

class KnownValues(TestCase):
pass

test_values = ((1, "I"), (2, "II"), (3, "III"), (4, "IV"), (5, "V"))
for a, r in test_values:
def test(self):


Change the above line to

def test(self, a=a, r=r):

or you will perform the test five times with (5, "V").
print a, r
result = roman.toRoman(a)
self.assertEqual(r, result)
setattr(KnownValues, "test_%s_%s" % (a, r), test)

test_suite = makeSuite(KnownValues, "test_")

if __name__ == "__main__":
main()
--- i2r.py ---

I like the idea, once the little error is removed. In general, I think the
unit test code should be as simple as possible. Otherwise we would need
unit tests for unit tests for...

Peter

Jul 18 '05 #7
Peter Hansen <pe***@engcorp.com> wrote in message news:<3F***************@engcorp.com>...
Paul Moore wrote:

Can anyone suggest a more reasonable way of running this sort of
table-driven test via unittest?


Why not just extend self.assertEqual() and use your own check, with
additional logic as required to increment counters or add items
to the list of passing tests. Then put a final check of the number
of passing tests or something like that at the end to make sure
things worked overall.
For example:

class KnownValues(unittest.TestCase):
def setUp(self):
self.passCount = 0

def checkValue(self, expected, result):
if expected == result:
self.passCount += 1
else:
# left as exercise to the reader, but pass would work...

def testToRomanKnownValues(self):
for integer, numeral in self.knownValues:
result = roman.toRoman(integer)
self.checkValue(numeral, result)
self.assertEqual(len(self.knownValues), self.passCount)

No, you don't get the psychologically affirming "52 tests passed!"
without changes to the TestRunner, but I assume the non-cosmetic part
of this is more your concern right now...


I don't see the remainder of the problem as "non-cosmetic". The error
report I get (or rather the information it offers) is

1 test failed - pass count is 25 instead of 52.

But that doesn't tell me *which* tests failed.

The key point here is that I'm NOT running one test - I really am
running 52 distinct tests. OK, they are all very similar, differing
only in the data - but for pity's sake, isn't that what an object
oriented structure is supposed to make easy???

Paul.
Jul 18 '05 #8
David Goodger <go*****@python.org> wrote in message news:<ma*************************************@pyth on.org>...
Paul Moore wrote:
>>> Can anyone suggest a more reasonable way of running this sort of
>>> table-driven test via unittest?


Take a look at Docutils' test/DocutilsTestSupport.py for ideas. The
CustomTestSuite and CustomTestCase classes provide support for named
data-driven tests. Most of Docutils' tests are data-driven.


Urk. That's hairy stuff. Thanks for the pointer, I'll do some research.

But I still think that this sort of thing should be easy :-(

Paul.
Jul 18 '05 #9
At 3:11 AM -0700 10/22/03, Paul Moore wrote:
Peter Hansen <pe***@engcorp.com> wrote in message
news:<3F***************@engcorp.com>...
> > No, you don't get the psychologically affirming "52 tests passed!" without changes to the TestRunner, but I assume the non-cosmetic part
of this is more your concern right now...


I don't see the remainder of the problem as "non-cosmetic". The error
report I get (or rather the information it offers) is

1 test failed - pass count is 25 instead of 52.

But that doesn't tell me *which* tests failed.


Are you running the tests verbosely? eg. with a -v argument under
UNIX, or as specified in the docs
<http://www.python.org/doc/current/lib/minimal-example.html>? I get
the following output when using Miki's code (with a deliberate error
thrown in, and the print statement commented out):

bash-2.05b$ ./test.py -v
test_1_I (__main__.KnownValues) ... ok
test_2_II (__main__.KnownValues) ... ok
test_3_II (__main__.KnownValues) ... FAIL
test_4_IV (__main__.KnownValues) ... ok
test_5_V (__main__.KnownValues) ... ok

================================================== ====================
FAIL: test_3_II (__main__.KnownValues)
----------------------------------------------------------------------
Traceback (most recent call last):
File "./test.py", line 21, in test
self.assertEqual(r, result)
File "/usr/local/lib/python2.3/unittest.py", line 292, in failUnlessEqual
raise self.failureException, \
AssertionError: 'II' != 'III'

----------------------------------------------------------------------
Ran 5 tests in 0.055s

FAILED (failures=1)
bash-2.05b$
The key point here is that I'm NOT running one test - I really am
running 52 distinct tests. OK, they are all very similar, differing
only in the data - but for pity's sake, isn't that what an object
oriented structure is supposed to make easy???


Well, you're testing one aspect of the code. It's really just a
question of how you think about your tests.

Anthony
--
----------------------------------------------------
HyPEraCtiVE? HeY, WhO aRE YoU cALliNg HypERaCtIve?!
aB*****@wEStNeT.cOm.aU
----------------------------------------------------

Jul 18 '05 #10
Paul Moore wrote:

Peter Hansen <pe***@engcorp.com> wrote in message news:<3F***************@engcorp.com>...
Paul Moore wrote:

Can anyone suggest a more reasonable way of running this sort of
table-driven test via unittest?
[snip] No, you don't get the psychologically affirming "52 tests passed!"
without changes to the TestRunner, but I assume the non-cosmetic part
of this is more your concern right now...


I don't see the remainder of the problem as "non-cosmetic". The error
report I get (or rather the information it offers) is

1 test failed - pass count is 25 instead of 52.

But that doesn't tell me *which* tests failed.

The key point here is that I'm NOT running one test - I really am
running 52 distinct tests. OK, they are all very similar, differing
only in the data - but for pity's sake, isn't that what an object
oriented structure is supposed to make easy???


Well, look at it this way. Using the built-in assertEquals() and
similar functions is a way of explicitly asking for a test method to
abort, and for the framework to continue on with the next test
method. If you don't want that behaviour, nothing's forcing you
to use assertEqual().

Instead, just write up your own comparison routine, which doesn't
abort, and have verbose output for each failure. Something I've
done repeatedly in the past is to have a routine which says simply
(upon failure) "case %s: expected %s, result %s" and then substitute
in the test case input, the expected value, and the actual result.

The part that is "cosmetic" is insisting that this has to result
in the framework reporting the as individual "test" failures. To
do that, you need more extensive modifications, because unittest
has a clear, simple definition of what constitutes a test, and
individual comparisons inside a testMethod() are not it... it's the
whole test method that is a test.

After all, just because a test has two self.assertEquals() and a single
self.assert_() doesn't necessarily mean it's *three* tests.

As Anthony B. wrote, you're testing one "aspect" or something... don't
think of tests as calls to assertXxxx() methods, think of them as
collections of such calls.

-Peter
Jul 18 '05 #11

"Paul Moore" <pa********@atosorigin.com> wrote in message
news:18*************************@posting.google.co m...
David Goodger <go*****@python.org> wrote in message

news:<ma*************************************@pyth on.org>...
Paul Moore wrote:
>>> Can anyone suggest a more reasonable way of running this sort of
>>> table-driven test via unittest?


Take a look at Docutils' test/DocutilsTestSupport.py for ideas. The
CustomTestSuite and CustomTestCase classes provide support for named
data-driven tests. Most of Docutils' tests are data-driven.


Urk. That's hairy stuff. Thanks for the pointer, I'll do some research.

But I still think that this sort of thing should be easy :-(


If you find unnittest too heavy handed, you may want to take a look at
doctest. Essentially, doctest will test code found in docstrings and
compare results. This allows you to paste an interactive session into your
module.

HTH,

Emile van Sebille
em***@fenx.com
Jul 18 '05 #12
> Take a look at Docutils' test/DocutilsTestSupport.py for ideas. The
CustomTestSuite and CustomTestCase classes provide support for named
data-driven tests. Most of Docutils' tests are data-driven.
Ha! You beat me to it. And here I was ready to post my article on self-
loading test suites:

http://www.pycs.net/users/0000088/stories/7.html

Apart from the default TestLoader not picking up TestSuite subclasses,
which can be fixed with a modified main test running script, it all
works pretty well.
Ian Bicking wrote:
> unittest is not written with subclassing in mind, except for the
> limited subclassing that is documented. (And it uses
> double-underscore variables, like it's just *trying* to piss me off!
> Double-underscore variables are so arrogant and patronizing.


All very true. Double-underscores ought to be banned from the
standard library. They inevitably get in the way because no matter
how well a class is written, somebody is going to want to subclass it
in a way the original author never considered.


Amen to that. I complain in my article about having to hack around
unittest's double-underscored attributes. Worse, I never found a way to
call double-underscored methods. Bah humbug.

Back to tests, though; I imagine it wouldn't take much to merge a few of
these wheel implementations and produce a new, improved unittest.

Regards,
Garth.
Jul 18 '05 #13

This thread has been closed and replies have been disabled. Please start a new discussion.

Similar topics

3
by: Richard Wesley | last post by:
Hi all - I am trying to retrofit some units test into our code using the unittest module. I can get individual rewrites to work, but I am having trouble writing the "one test to run them all...
3
by: Gonçalo Rodrigues | last post by:
Hi, I use the unittest module for testing a given module's functionality, by lumping several test classes and then at the end a simple if __name__ == "__main__": unittest.main() In one of...
0
by: Remy Blank | last post by:
Ok, here we go. I added the possibility for tests using the unittest.py framework to be skipped. Basically, I added two methods to TestCase: TestCase.skip(msg): skips unconditionally...
41
by: Roy Smith | last post by:
I've used the standard unittest (pyunit) module on a few projects in the past and have always thought it basicly worked fine but was just a little too complicated for what it did. I'm starting a...
7
by: Jorgen Grahn | last post by:
I have a set of tests in different modules: test_foo.py, test_bar.py and so on. All of these use the simplest possible internal layout: a number of classes containing test*() methods, and the good...
3
by: David Vincent | last post by:
-----BEGIN PGP SIGNED MESSAGE----- Hello I'm hoping to get some insight into a situation that seems odd to me. My Python experience is limited; I've just started using the unittest module....
0
by: Chris Fonnesbeck | last post by:
I have built the following unit test, observing the examples laid out in the python docs: class testMCMC(unittest.TestCase): def setUp(self): # Create an instance of the sampler...
1
by: Chris Fonnesbeck | last post by:
I have a module for which I am trying to code a unit test. However, when I run unittest.main(), I get: In : import PyMC In : PyMC.unittest.main() ...
3
by: Paul Moore | last post by:
My normal testing consists of a tests.py script using unittest, with the basic if __name__ == '__main__': unittest.main() incantation to get things going. But I now want to incorporate...
0
by: aa123db | last post by:
Variable and constants Use var or let for variables and const fror constants. Var foo ='bar'; Let foo ='bar';const baz ='bar'; Functions function $name$ ($parameters$) { } ...
0
by: ryjfgjl | last post by:
In our work, we often receive Excel tables with data in the same format. If we want to analyze these data, it can be difficult to analyze them because the data is spread across multiple Excel files...
0
by: emmanuelkatto | last post by:
Hi All, I am Emmanuel katto from Uganda. I want to ask what challenges you've faced while migrating a website to cloud. Please let me know. Thanks! Emmanuel
0
BarryA
by: BarryA | last post by:
What are the essential steps and strategies outlined in the Data Structures and Algorithms (DSA) roadmap for aspiring data scientists? How can individuals effectively utilize this roadmap to progress...
1
by: nemocccc | last post by:
hello, everyone, I want to develop a software for my android phone for daily needs, any suggestions?
1
by: Sonnysonu | last post by:
This is the data of csv file 1 2 3 1 2 3 1 2 3 1 2 3 2 3 2 3 3 the lengths should be different i have to store the data by column-wise with in the specific length. suppose the i have to...
0
marktang
by: marktang | last post by:
ONU (Optical Network Unit) is one of the key components for providing high-speed Internet services. Its primary function is to act as an endpoint device located at the user's premises. However,...
0
by: Hystou | last post by:
Most computers default to English, but sometimes we require a different language, especially when relocating. Forgot to request a specific language before your computer shipped? No problem! You can...
0
Oralloy
by: Oralloy | last post by:
Hello folks, I am unable to find appropriate documentation on the type promotion of bit-fields when using the generalised comparison operator "<=>". The problem is that using the GNU compilers,...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.