unit testing and comparing dictionaries

3,729 views
Skip to first unread message

Gene Campbell

unread,
Jun 17, 2008, 1:46:53 AM6/17/08
to django...@googlegroups.com
Hello Djangonauts

I'm a noob on both Django and Python, so this question might be easy
for the experts out there. I am trying to do test first development
during the development of my model code. (I have lots of experience
with test first coding in the Java world, no practical experience in
the Py world.)

I want to do something like this

class ModelTest(TestCase):

def test_crud_modelone(self):
mo = ModelOne( name="A name" )
mo.save()

saved_mo = mo.objects.get(pk=mo.id)

assertEqual( vars(mo), vars(saved_mo),"Something not getting saved")

That assertEqual line fails. I wrote this

def compare(self, obj1, obj2, verbose = False):
other = vars(obj2)
for d,v in vars(obj1).items():
if isinstance(other.get(d,None),datetime.date):
if not other.get(d,None) == v.date():
return False
elif not other.get(d,None) == v:
return False

return True

This is probably horrible python, I'm still learning (unlearning
Java), but it compares correctly for strings and dates so far that
I've tested.

My questions are 1) Does anything like this already exist in Python
or Django that I should use? 2) If not, would something like this be
valuable to polish up and commit to the Django project?

Some might be asking why I'm testing if my models can be saved, as
most of that functionality is tested when Django is tested. But, I
still feel there's a enough that I have to write to warrant a simple
sanity test like this.

-- If I'm a million miles off base, I don't mind being told so. :)


--
Picante Solutions Limited
e: ge...@picante.co.nz
w: 06 757 9488

Norman Harman

unread,
Jun 17, 2008, 10:48:25 AM6/17/08
to django...@googlegroups.com
I've been using python since 1.5, I had to look up what vars() did.
Probably not idiomatic Python. (I've always just looked at __dict__
directly, which I do rarely).

The assertEqual line (probably) fails because the order of dictionary
keys is not defined.

> Some might be asking why I'm testing if my models can be saved, as
> most of that functionality is tested when Django is tested. But, I
> still feel there's a enough that I have to write to warrant a simple
> sanity test like this.

FWIW I believe testing what you're testing is a waste of time and
violates unit testing. Really the db should be mocked, but that is
fairly hard. If you didn't write save(), you shouldn't be testing it.


But, if I were testing this I think I'd do something like this
assertEqual( db_mo, mo, "local and db versions of %s not
identical" % mo.__class__)

In general, I trust == (either Python's or the object's author's) more
than poking into the internals of an object, encapsulation and all that :)


When I compare dictionaries I either convert them to sorted tuples (to
put keys in proper order)

expected = dict(var1="dog", var2="cat")
answer = something_that_return_dict()
assertEqual( sorted(expected.items()), sorted(answer.items()) )

Or check only certain keys are equal (for instance if the answer dict
has other stuff in it I don't care about)

check_these = ["key1", "key3"] # = expected_dict.keys()
answer = something_that_return_dict()
for k in check_these:
self.assertEqual( expected_dict[k], answer[k],
"expected[%(key)s] != answer[%(key)s]" % {"key":k} )


For Models in which I write a custom save method this is how I usually
structure it.

def save(self):
if self.value is None:
self.value = self._calc_value()
super(FooModel, self).save()

def test_value_on_save(self):
test_data =( (dict(foo="", bar=""), 42 ),
(dict(foo="1", bar="3"), 37 ),
# test more edge cases, etc.
)
for (expected, data) in test_data
t = Model(**data)
t.save()
self.assertEqual( expected, t.value )

If I had another custom value I'd write another test for it.

Good luck with your continued Python adventures,
--
Norman J. Harman Jr.
Senior Web Specialist, Austin American-Statesman
___________________________________________________________________________
You've got fun! Check out Austin360.com for all the entertainment
info you need to live it up in the big city!

John

unread,
Jun 17, 2008, 3:59:40 PM6/17/08
to Django users
Dictionaries compare equal if they contain the same data, regardless
of key order. There is no need to convert to sorted sequences unless
you need to compare the serialized output (such as in doctests).

I would guess that one of the models has additional fields added to
its __dict__, perhaps through some caching mechanism. Try printing
``vars(mo)`` and ``vars(saved_mo)``, and then examine the output
manually. If you truly wish to examine two model objects for equality,
you could try the appended code[1]

However, I would advise that you remove your test entirely. It is
without point, as Django's unit testing already covers the behavior of
``Model.send()``. Restrict testing to your own code, before you go mad
"sanity checking" the massive pile of third-party code underlying
yours.

[1]
--------------------------
assertEqual (type (mo), type (saved_mo))

# Retrieve a list of database model field names
attrs = [f.attname for f in mo._meta.fields]

# Construct lists based on the model attributes, and compare them
mo_attrs = [getattr (mo, a) for a in attrs]
saved_mo_attrs = [getattr (saved_mo, a) for a in attrs]
assertEqual (mo_attrs, saved_mo_attrs)
--------------------------

Gene Campbell

unread,
Jun 17, 2008, 4:13:09 PM6/17/08
to django...@googlegroups.com
Thanks. In this case, I'm doing a bit extra in work order to learn
Python and Django.
And, I do see how testing models such that all the fields are equal is
a waste on the basis
that Django should be testing that that functionality works.

Gene Campbell

unread,
Jun 17, 2008, 4:40:41 PM6/17/08
to django...@googlegroups.com
Thanks for the insight! Do you write tests to check for constraints
that are optional in definitions, for example?

address4 = models.CharField(max_length=45, null=True, blank=True)

could be written as

address4 = models.CharField(max_length=45)

Were is the most reasonable place to test this is correct (not regressing)

What about uniqueness, and composite field uniqueness?
# unique_together = (("field1", "field2","field3"),)

I'm thinking it would make sense to try to
create 2 of an object and save them, and check that it fails

Norman Harman

unread,
Jun 17, 2008, 5:03:50 PM6/17/08
to django...@googlegroups.com
Gene Campbell wrote:
> Thanks for the insight! Do you write tests to check for constraints
> that are optional in definitions, for example?
>
> address4 = models.CharField(max_length=45, null=True, blank=True)
>
> could be written as
>
> address4 = models.CharField(max_length=45)
>
> Were is the most reasonable place to test this is correct (not regressing)

I'm not sure. I'm distraught over having validations in two places the
model and forms that "feed" that model. And form_from_model or whatever
it is called is never what I want. I tend to test the forms as I tend
to manipulate models through forms and the forms support much richer
validations than FooFields do.

Blank is an admin thing and I typically don't test those. Testing
Nullable field(s) would be data point(s) in creation/save test.

> What about uniqueness, and composite field uniqueness?
> # unique_together = (("field1", "field2","field3"),)
>
> I'm thinking it would make sense to try to
> create 2 of an object and save them, and check that it fails

Yep two of the objects whose 3 fields are not unique together.

Paul Winkler

unread,
Jun 17, 2008, 6:06:11 PM6/17/08
to Django users
On Jun 17, 1:46 am, "Gene Campbell" <geneyahoos...@gmail.com> wrote:
> Hello Djangonauts
>
> I'm a noob on both Django and Python, so this question might be easy
> for the experts out there. I am trying to do test first development
> during the development of my model code. (I have lots of experience
> with test first coding in the Java world, no practical experience in
> the Py world.)
>
> I want to do something like this
>
> class ModelTest(TestCase):
>
> def test_crud_modelone(self):
> mo = ModelOne( name="A name" )
> mo.save()
>
> saved_mo = mo.objects.get(pk=mo.id)
>
> assertEqual( vars(mo), vars(saved_mo),"Something not getting saved")
>
> That assertEqual line fails. I wrote this

You didn't say what exactly happens. If it's failing with a NameError,
notice that you forgot to specify self.assertEqual().
There's no implicit "this" like in Java.

And as others have mentioned, vars() is rarely used in practice.
And if there's a problem, it'll be hard to see what's wrong.
It's more tedious but ultimately more helpful to specify everything:

self.assertEqual(mo.foo, saved_mo.foo)
self.assertEqual(mo.bar, saved_mo.bar)

... and so forth.

Also, as a fellow django noob I'm not sure about this, but I seem to
recall that mo and saved_mo should be exactly the same object?
If that's true (hopefully somebody will correct me if not), it's kind
of a silly test; you might instead just do

self.failUnless(mo is saved_mo)


-- PW

Gene Campbell

unread,
Jun 17, 2008, 6:30:24 PM6/17/08
to django...@googlegroups.com
sorry, omitted 'self' by accident.

There are a couple things going on here. One is that type-o.
The other is more of an open question as to what should be tested, and how.

I'm coming around to the this.

for each model I'll will
1) assume that clients that use the model code will test it indirectly.
2) test that nullable fields accept nulls (Perhaps this isn't
necessary - see 1)
3) test that unique constraints can't be violated.
4) test that all business methods function properly (with probably
the exception of __unicode__

Basically, if I can forget to state that model field should be unique,
then I can accidently remove that too. I'd like to have a test cover
it. But, if you leave off a whole field, some other test should fail,
or perhaps your app doesn't need the field.

(Note this is somewhat stream of thinking, and I'm not an expert at
Python or Django!)

Reply all
Reply to author
Forward
0 new messages