If you are new to TDD, writing the tests might be a real pain, not to mention about worrying how fast they run.
Factory Boy
If you already are familiar or just getting started with OOP and feel comfortable with actually checking the attribute in order to make it obvious that the value was changed, then Factory Boy is really a tool for you.
It is a handy tool to help you get started with writing tests and checking how objects relate to each other.
The down-side is that you will have to always create a "context" to make sure that you have all the entries you need. Eg if you are adding an article, you first need to manually create (hardcode) the creation of a category.
Mock on the other hand, is a more advanced tool, and requires a totally different approach and even a different mentality about how the tests must be written and what should be tested.
Isolation is its main advantages and you dont have to create a "context" so that your tests can run.
The test's goal is to make sure that if some POST data is sent, then a .create() call is made with the correct arguments. The action done by. create() is checked in another test.
Not having to deal with DB or other hooks makes this approach the faster one.
The same table referenced by multiple Django models
Dealing with legacy code is not an easy thing to do.
Recently we have inherited some code that dealt with an API that provides a lot of data, in a frequent manner, and with the risk of data being incomplete.
The legacy code had some tables and models that dealt with this, and although the data relates to multiple tables, for database consistency, they have decided not to use FK, but instead IntegerField.
class Event(models.Model): articlefk = models.IntegerField(db_column='articleFK') date = models.DateField(max_length=150) order = models.IntegerField() # a long list of fields
When we had to display some data from these tables, we realized that it would increase the amount of queries executed with a few dozens.
The first thing that came to our mind, was keeping the DB with integer fields, but changing the Django model to use ForeignKey instead, so Django would know how to do its joins.
The ghost of legacy code struck us, because it was heavily relying on that IntegerField, raising a Type Error
TypeError: int() argument must be a string or a number, not 'Article'
Refactoring legacy code was not something we could add to the sprint's time frame, as we were behind schedule already (a separate issue was created to address this in the next release).
Having two models that referenced the same table, might do a trick for us. We would keep the legacy code as is, and we could have Django do our joins.
# legacy.models class Event(models.Model): # legacy model articlefk = models.IntegerField(db_column='articleFK') date = models.DateField(max_length=150) order = models.IntegerField() # a long list of fields
# new_app.models class Event(models.Model): article = models.ForeignKey('legacy.Article', db_column='articleFK') date = models.DateField(max_length=150) order = models.IntegerField() # the same long list of fields as above class Meta: db_table = u'legacy_event' managed = False
Notice we added "managed = False" as we didn't want syncdb to worry about this model.
Django was fine at this point, both legacy and new app were working fine.
Code cleanliness is something that you should worry when writing software, and this was a situation where we had to clean the mess.
Having both fields define the complete list of fields was not DRY, and we couldn't live with that.
This is how we DRYed our code in the end:
# legacy.models class EventBase(models.Model): date = models.DateField(max_length=150) order = models.IntegerField() # a long list of fields class Meta: abstract = True class Event(EventBase): # legacy model articlefk = models.IntegerField(db_column='articleFK')
# new_app.models from legacy.models import EventBase class Event(EventBase): article = models.ForeignKey('legacy.Article', db_column='articleFK') class Meta: db_table = u'legacy_event' managed = False
Using an abstract model that defines all the fields, except for the FK/Integer ones, allowed us to maintain legacy code functionality, add new features on top of that, and prove to ourselves that we could use multiple models pointing to the same DB table in a clean and DRY way.