question about an exciting gotcha for unittests (and elsewhere) ...

C

Cameron Simpson

I've run into a problem unittesting something I'm writing.

I have a little node tracking class I'm using to track items and
attributes. An item is a "Node" object, and the collection is a
"NodeDB".

So I'm writing some unittests, thus:

class Node(dict): [...]
class NodeDB(dic): [...]
class Backend(object):
def serialise(self, value):
''' Convert a value for external string storage.
'''
if isinstance(value, Node): [...]
return ":%s:%s" % (value.type, value.name)
t = type(value)
assert t in (str,int), repr(t)+" "+repr(value)+" "+repr(Node)
[...]

class TestAll(unittest.TestCase):
def setUp(self):
from cs.nodedb.sqla import Backend_SQLAlchemy
self.backend=Backend_SQLAlchemy('sqlite:///:memory:')
self.db=NodeDB(backend=self.backend)
def test01serialise(self):
H = self.db.newNode('HOST', 'foo')
for value in 1, 'str1', ':str2', '::', H:
s = self.backend.serialise(value)
assert type(s) is str
self.assert_(value == self.backend.deserialise(s))
[...]

if __name__ == '__main__':
import sqlalchemy
print 'SQLAlchemy version =', sqlalchemy.__version__
unittest.main()

and it's failing. I've traced the failure cause, ending up with this
assertion message from the end of serialise() above:

AssertionError: <class '__main__.Node'> HOST:foo:{} <class 'cs.nodedb.node.Node'>

Experienced users will see at once what's happened: I've made a Node
myself in the test using the local class, and the Node class is thus
__main__.Node. However, my sql Backend class has independently imported
the "Node" and "Backend" classes from "cs.nodedb.node". So when _it_
calls serialise(), "Node" is "cs.nodedb.node.Node".

And lo, the:

if isinstance(value, Node):

test at the top of serialise() fails.

What's a sensible way of doing this correctly?

I _don't_ want to duck-type the Node and merely test for "type" and "name"
values, because I want to be rather picky about what gets into the backend
database - the wrong types indicate bad app code, and should not be allowed
to introduce corrupt database values.

Cheers,
--
Cameron Simpson <[email protected]> DoD#743
http://www.cskk.ezoshosting.com/cs/

That particular mistake will not be repeated. There are plenty of mistakes
left that have not yet been used. - Andy Tanenbaum <[email protected]>
 
P

Peter Otten

Cameron said:
and it's failing. I've traced the failure cause, ending up with this
assertion message from the end of serialise() above:

AssertionError: <class '__main__.Node'> HOST:foo:{} <class
'cs.nodedb.node.Node'>

Experienced users will see at once what's happened: I've made a Node
myself in the test using the local class, and the Node class is thus
__main__.Node. However, my sql Backend class has independently imported
the "Node" and "Backend" classes from "cs.nodedb.node". So when _it_
calls serialise(), "Node" is "cs.nodedb.node.Node".

And lo, the:

if isinstance(value, Node):

test at the top of serialise() fails.

What's a sensible way of doing this correctly?

Move the unit tests into a separate script and have that import the module
cs.nodedb.node.

In general, avoid importing a module and at the same time using it as the
main script. When persistent objects are involved "the same time" can even
spread over distinct runs of separate scripts accessing the same data.

Obvious? But you asked for a /sensible/ way.

Peter
 
C

Cameron Simpson

| Cameron Simpson wrote:
| > and it's failing. I've traced the failure cause, ending up with this
| > assertion message from the end of serialise() above:
| >
| > AssertionError: <class '__main__.Node'> HOST:foo:{} <class
| > 'cs.nodedb.node.Node'>
| >
| > Experienced users will see at once what's happened: I've made a Node
| > myself in the test using the local class, and the Node class is thus
| > __main__.Node. However, my sql Backend class has independently imported
| > the "Node" and "Backend" classes from "cs.nodedb.node". So when _it_
| > calls serialise(), "Node" is "cs.nodedb.node.Node".
| >
| > And lo, the:
| > if isinstance(value, Node):
| > test at the top of serialise() fails.
| >
| > What's a sensible way of doing this correctly?
|
| Move the unit tests into a separate script and have that import the module
| cs.nodedb.node.

Hmm. I have been very attracted by the idea of having the unittest in
the module itself, and run if I "run" the module alone (instead of
importing it).

Conversely, I've also got a few modules that are standalone programs
and running _then_ runs the app, not a bunch of tests.

Having the tests elsewhere would solve the problem as you say; an import
of the module is what "real" code will be doing and, as my misadventure
above demonstrates, that's a different thing as far as namespaces go.

| In general, avoid importing a module and at the same time using it as the
| main script.

Fsir enough - that's a succinct description of what I did wrong.

| When persistent objects are involved "the same time" can even
| spread over distinct runs of separate scripts accessing the same data.

Good point; this is another thing to add to my reluctance to use
persistent objects. Persistent _data_, sure, and my little nodedb
package is exactly such an animal, but not persistent objects.

| Obvious? But you asked for a /sensible/ way.

Indeed. Thanks for the suggestion. I'll try to pursue it; the rest of my
thread shows I've worked around some of the problem but the workaround
still depends somewhat on the package internal dependencies and going
your way with a separate test script that does a proper import will
avoid that weakness.

Cheers,
--
Cameron Simpson <[email protected]> DoD#743
http://www.cskk.ezoshosting.com/cs/

Thought: Why does man kill? He kills for food. And not only for
food: frequently there must be a beverage. - Woody Allen
 
T

Terry Reedy

On 23Apr2010 13:25, Peter Otten<[email protected]> wrote:
| Move the unit tests into a separate script and have that import the module
| cs.nodedb.node.

Hmm. I have been very attracted by the idea of having the unittest in
the module itself, and run if I "run" the module alone (instead of
importing it).

I do this too. edit, run (F5 in IDLE), (click back to edit window) edit,
run, ... several times in an hour. Very nice.

But... I have simpler modules (function, not class definitions) and no
circular imports. So I was thinking the same thing Peter said for your case.
Conversely, I've also got a few modules that are standalone programs
and running _then_ runs the app, not a bunch of tests.

Having the tests elsewhere would solve the problem as you say; an import
of the module is what "real" code will be doing and, as my misadventure
above demonstrates, that's a different thing as far as namespaces go.

| In general, avoid importing a module and at the same time using it as the
| main script.

Fsir enough - that's a succinct description of what I did wrong.

Several people have posted problems that amount to this. An 'exciting
gotcha' indeed ;-).

Terry Jan Reedy
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,968
Messages
2,570,149
Members
46,695
Latest member
StanleyDri

Latest Threads

Top