views:

21

answers:

1

I want to make sure I am testing Models/Objects in isolation and not as one huge system.

If I have an Order object and it has Foreign Keys to Customers, Payments, OrderItems, etc. and I want to test Order functionality, I need to create fixtures for all of that related data, or create it in code. I think what I really need to be doing is mocking out the other items, but I don't see an easy (or possible) solution for that if I am doing queries on these Foreign Keys.

The common solutions (fixtures) don't really let me test one Object at a time. I am sure this is partly caused by my app being way over coupled.

I am trying my darndest to adopt TDD as my main method of working, but the way things work with Django, it seems you can either run very trivial unit tests, or these massive integration tests.

[Edit] Better explicit example and a some more humility

What I mean is that I seem to be able to only run trivial unit tests. I have seen people with very well tested and granular modules. I am certain some of this can be followed back to poor design.

Example:

I have a model call Upsell which is linked to a Product model. Then I have a Choices model which are children of Upsell (do you want what's behind door #1, #2, #3).

The Upsell model has several methods on it that derive items necessary to render the template from their choices. The most important one is that it creates a URL for each choice. It does this through some string mangling etc. If I wanted to test the Upsell.get_urls() method, I want to have it not depend on the values of choices in the fixtures, and I want to not have it depend on the value of Product in the fixtures.

Right now I populate the db in the setUp method for the tests, and that works well with the way Django backs out the transaction every time, but only outside of setUp and tearDown. This works fairly well except some of the Models are fairly complex to set up, while I actually only need to get one attribute for it.

I can't give you an example of that, since I can't accomplish it, but here is the type of thing I am doing now. Basically I input an entire order, create the A/B experiment it was attached to, etc. And that's not counting Product, Categories, etc. all set up by fixtures. It's not the extra work I am concerned as I can't even test one database-based object at a time. The Tests below are important, but they are integration tests. I would like to be building up to something like this by testing each item separately. As you pointed out, maybe I shouldn't have chosen a framework so closely tied to the db. Does any sort of dependency injection exist with something like this? (beyond my testing, but the code itself as well)

class TestMultiSinglePaySwap(TestCase):
    fixtures = ['/srv/asm/fixtures/alchemysites.json','/srv/asm/fixtures/catalog.json','/srv/asm/fixtures/checkout_smallset.json','/srv/asm/fixtures/order-test-fixture.json','/srv/asm/fixtures/offers.json']

def setUp(self):
    self.o = Order()
    self.sp = SiteProfile.objects.get(pk=1)
    self.c = Customer.objects.get(pk=1)
    signals.post_save.disconnect(order_email_first, sender=Order)
    self.o.customer = self.c
    p = Payment()
    p.cc_number = '4444000011110000'
    p.cc_exp_month = '12'
    p.cc_type = 'V'
    p.cc_exp_year = '2020'
    p.cvv2 = '123'
    p.save()
    self.o.payment = p
    self.o.site_profile = self.sp
    self.o.save()
    self.initial_items = []
    self.main_kit = Product.objects.get(pk='MOA1000D6')
    self.initial_items.append(self.main_kit)
    self.o.add_item('MOA1000D6', 1, False)
    self.item1 = Product.objects.get(pk='MOA1041A-6')
    self.initial_items.append(self.item1)
    self.o.add_item('MOA1041A-6', 1, False)
    self.item2 = Product.objects.get(pk='MOA1015-6B')
    self.initial_items.append(self.item2)
    self.o.add_item('MOA1015-6B', 1, False)
    self.item3 = Product.objects.get(pk='STP1001-6E')
    self.initial_items.append(self.item3)
    self.o.add_item('STP1001-6E', 1, False)
    self.swap_item1 = Product.objects.get(pk='MOA1041A-1')

def test_single_pay_swap_wholeorder(self):
    o = self.o
    swap_all_skus(o)
    post_swap_order = Order.objects.get(pk = o.id)
    swapped_skus = ['MOA1000D','MOA1041A-1','MOA1015-1B','STP1001-1E']
    order_items = post_swap_order.get_all_line_items()
    self.assertEqual(order_items.count(), 4)
    pr1 = Product()
    pr1.sku = 'MOA1000D'
    item = OrderItem.objects.get(order = o, sku = 'MOA1000D') 
    self.assertTrue(item.sku.sku == 'MOA1000D')
    pr2 = Product()
    pr2.sku = 'MOA1015-1B'
    item = OrderItem.objects.get(order = o, sku = 'MOA1015-1B') 
    self.assertTrue(item.sku.sku == 'MOA1015-1B')
    pr1 = Product()
    pr1.sku = 'MOA1041A-1'
    item = OrderItem.objects.get(order = o, sku = 'MOA1041A-1') 
    self.assertTrue(item.sku.sku == 'MOA1041A-1')
    pr1 = Product()
    pr1.sku = 'STP1001-1E'
    item = OrderItem.objects.get(order = o, sku = 'STP1001-1E') 
    self.assertTrue(item.sku.sku == 'STP1001-1E')

Note that I have never actually used a Mock framework though I have tried. So I may also just be fundamentally missing something here.

A: 

This will probably not answer your question but it may give you food for thought.

In my opinion when you are testing a database backed project or application there is a limit to what you can mock. This is especially so when you are using a framework and an ORM such as the one Django offers. In Django there is no distinction between the business model class and the persistence model class. If you want such a distinction then you'll have to add it yourself.

Unless you are willing to add that additional layer of complexity yourself it becomes tricky to test the business objects alone without having to add fixtures etc. If you must do so you will have to tackle some of the auto magic vodoo done by Django.

If you do choose to grit your teeth and dig in then Michael Foord's Python Mock library will come in quite handy.

I am trying my darndest to adopt TDD as my main method of working, but the way things work with Django, it seems you can either run very trivial unit tests, or these massive integration tests.

I have used Django unit testing mechanism to write non-trivial unit tests. My requirements were doubtless very different from yours. If you can provide more specific details about what you are trying to accomplish then users here would be able to suggest other alternatives.

Manoj Govindan
I think when you said "there is no distinction between the business model class and the persistence model class" you hit the nail right on the head. Django gives you the test database option and that's as close to mocking as could be expected (although there could be a "mock" db backend that would only enforce the constraints you want). At least now I know that I am not looking for something that's already written.
zenWeasel