I'm really struggling to write effective unit tests for a large Django project. I have reasonably good test coverage, but I've come to realize that the tests I've been writing are definitely integration/acceptance tests, not unit tests at all, and I have critical portions of my application that are not being tested effectively. I want to fix this ASAP.
Here's my problem. My schema is deeply relational, and heavily time-oriented, giving my model object high internal coupling and lots of state. Many of my model methods query based on time intervals, and I've got a lot of auto_now_add going on in timestamped fields. So take a method that looks like this for example:
def summary(self, startTime=None, endTime=None):
# ... logic to assign a proper start and end time
# if none was provided, probably using datetime.now()
objects = self.related_model_set.manager_method.filter(...)
return sum(object.methodkey_method(startTime, endTime) for object in objects)
How does one approach testing something like this?
Here's where I am so far. It occurs to me that the unit testing objective should be given some mocked behavior by key_method on its arguments, is summary correctly filtering/aggregating to produce a correct result?
Mocking datetime.now() is straightforward enough, but how can I mock out the rest of the behavior?
- Mocking datetime.now() is straightforward enoughI could use fixtures, but how can I unit test the rest of the functionality? I'veI've heard pros and cons of using fixtures for building my data (poor maintainability being a con that hits home for me).
- I could also setup my data through the ORM, but that can be limiting as well, because then I have to create related objects as well. And the ORM doesn't let you mess with
auto_now_addfields manually. - Mocking the ORM is another option, but not only is it tricky to mock deeply nested ORM methods, but the logic in the ORM code gets mocked out of the test, and mocking seems to make the test really dependent on the internals and dependencies of the function-under-test.
The toughest nuts to crack seem to be the functions like this, that sit on a few layers of models and lower-level functions and are very dependent on the time, even though these functions may not be super complicated. My overall problem is that no matter how I seem to slice it, my tests are looking way more complex than the functions they are testing. I think I've gone off-track someplace, because I don't think it should be like that. Perhaps my function should be broken into smaller chunks, but it's not that big to begin with, and I don't want to end up with ravioli.