A Question About Nose
I’m putting together an episode of the testing lecture to introduce unit testing frameworks. In the past, I’ve used unittest, but colleagues have had good experiences with Nose, which doesn’t require students to understand classes and methods in order to write tests. As an example, they’re going to test a function that finds the overlap between fields in aerial photographs of Saskatchewan (where it’s safe to assume that everything is a rectangle
).
My question is, does Nose already have a tool for running through a table of fixtures and expected results? My hand-rolled version is:
Tests = (
# R1 R2 Expected
( ((0, 0), (0, 0)), ((0, 0), (0, 0)), None ),
( ((0, 0), (0, 0)), ((0, 0), (1, 1)), None ),
( ((0, 0), (1, 1)), ((0, 0), (1, 1)), ((0, 0), (1, 1)) ),
( ((0, 3), (2, 5)), ((1, 0), (2, 4)), ((1, 3), (2, 4)) )
)
def test_table():
for (R1, R2, expected) in Tests:
yield run_it, R1, R2, expected
def run_it(R1, R2, expected):
assert overlap(R1, R2) == expected
which is simple enough if students already understand generators and function application, but hell to explain if they don’t—and they won’t. So, is this already in Nose, and I’ve just missed it? If not, who wants to help me design it, and what’s the likely elapsed time between submission of a patch and its appearance in a release?

Yup, Nose already does this; Nose calls the feature “test generators” — see http://somethingaboutorange.com/mrl/projects/nose/0.11.1/writing_tests.html#test-generators.
It’s a little different than your example (you yield the assertion call, not expected values) but it works basically like you’ve spec’d.
I’ve been using this a bunch recently; see http://github.com/jacobian/python-storymarket/blob/master/tests/test_content.py#L23 for a few recent examples.
@Jacob …but the tester has to write code with a ‘yield’ in it, right? I want to hide the ‘yield’, so that all the tester sees is the table and the testing function, so that I don’t have to explain what ‘yield’ does. Hm… should be possible to write a decorator:
Tests = (
…as in the original post…
)
@test_from_table(Tests):
def run_it(arg1, arg2, arg3):
assert overlap(arg1, arg2) == arg3
Since the test table really depends on the function, (and since you’ve got the results in there, I assert that this test table will _always) depend on the function,) why bother with the generator? Wouldn’t it be just as easy to write:
def test_table():
for (R1, R2, expected) in Tests:
assert overlap(R1, R2) == expected
? No generators, no yield, no function application. (Yeah, the line number wouldn’t be as useful in a failure, but you could add a message, or change it to “for i, (R1, R2, expected) in enumerate(Tests):”, and include the table line number…)
Later,
Blake.
@Blake The problem with an explicit loop is that if one of the tests fails, none of the subsequent tests will be run. Or am I missing something?
OK, here’s a (failed) first cut at what I want:
def tabletest(table):
..def inner(function):
….for args in table:
……all = tuple([function] + list(args))
……yield all
..return inner
Table = (
..(1, 2),
..(4, 3)
)
@tabletest(Table)
def pairing(left, right):
..assert left > right
(I’m using ‘.’ instead of space because this editor won’t let me put in a non-breaking space.) The decorator ‘tabletest’ creates a generator that yields a tuple consisting of the test function matched with each set of arguments from the table. We then apply that decorator to the combination of ‘Table’ and ‘pairing’, which are the fixtures and the text function.
Problem is, Nose doesn’t notice this, because the name isn’t right. I’ve tried changing the name of ‘pairing’ to ‘pairing_test’ and ‘test_pairing’, but Nose still won’t notice it. What am I missing?
I don’t know what you’re missing, but here’s what I see:
Adding print statements shows that while tabletest is being called, inner is never being called.
The other difference I notice between your code (test_pairing/test_inner) and the example is:
(Pdb) test_evens
(Pdb) test_pairing
Does that matter? Maybe. Why is test_evens not a generator? No idea!
Later,
Blake.
(Uh, that would have been:
(Pdb) test_evens
function test_evens at 0x101fe9488
(Pdb) test_pairing
generator object test_inner at 0x101fd2d70
but with extra angle brackets which made all sorts of stuff disappear.
I think Michael Foord played with something like this on top of unittest… yeah: http://code.google.com/p/unittest-ext/source/browse/trunk/params.py
To make it (or anything) work with Nose, you’d need to make sure that whatever the decorator returns — “inner” in your example — is something that Nose considers a test; see http://somethingaboutorange.com/mrl/projects/nose/0.11.2/finding_tests.html for some info about how Nose finds tests. Looking at the code, I think it’s just introspecting __name__ for everything in the scope. So I think in your case the easiest thing to do would be to rename “inner” to “test_inner” or something. Though perhaps you might need a unique name for each use of the decorator, so you might need to assign to inner.__name__ before returning it.
Got it! http://gist.github.com/508237