In this part of the Testing Django series we will learn how to set up a dedicated CouchDB database for our automated Django tests. It builds on the previous parts that can be found here:
- Testing Django - part 1 - nose
- Testing Django - part 2 - lettuce
- Testing Django - part 3 - tdaemon
- Testing Django - part 4 - setting up a dedicated CouchDB for tests
Django supports different SQL systems natively. If you want to use NoSQL solutions for making your data persistent you need to tinker a little bit yourself sometimes. I used CouchDB and Couchdbkit in a Django project. One problem was that the test data was saved in the same CouchDB database as data that was entered through the "productive" web interface. Here I describe how to have separate a database for each case.
So let us get into the described trouble by changing our model to be made persistent as CouchDB document.
First we install Couchdbkit:
$ sudo easy_install -U Couchdbkit
Then we have to modify the settings.py so that our Django project is aware of it:
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'couchdbkit.ext.django', # Add this
'fruitsalad.fruits', # Added in previous parts
'django_nose', # Added in previous parts
'lettuce.django', # Added in previous parts
)
And we have to specify (also in settings.py) a database to use. The database does not have to exist as Couchdbkit will take care of this.
COUCHDB_DATABASES = (
('fruitsalad.fruits', 'http://127.0.0.1:5984/fruits')
)
Then we adapt our model:
from couchdbkit.ext.django.schema import *
class Fruit(Document):
name = StringProperty()
color = StringProperty()
def set_name(self, name):
self.name = name
def set_color(self, color):
self.color = color
def is_yummy(self):
return(True)
def become_brown(self):
self.color = "brown"
def disappear(self):
self.color = "transparent"
Okay, let's use Django's shell to create such a fruit object and save it to the CouchDB database.
$ ./manage.py shell Python 2.6.5 Type "copyright", "credits" or "license" for more information. IPython 0.10 -- An enhanced Interactive Python. ? -> Introduction and overview of IPython's features. %quickref -> Quick reference. help -> Python's own help system. object? -> Details about 'object'. ?object also works, ?? prints more. In [1]: from fruitsalad.fruits.models import Fruit In [2]: f = Fruit() In [3]: f.name = "Banana" In [4]: f.color = "yellow" In [5]: f.save()
We can check the existence by using curl:
$ curl -X GET http://127.0.0.1:5984/fruits/_all_docs
{"total_rows":1,"offset":0,"rows":[
{"id":"4292816409d02b99378f31dd79e9b347","key":"4292816409d02b99378f31dd79e9b347","value":{"rev":"1-88a1fdab4ca60892f3143ba09cdf6205"}}
]}
This means we have (as expected) one document in the database. To have a closer look at it:
$ curl -X GET http://127.0.0.1:5984/fruits/4292816409d02b99378f31dd79e9b347
{"_id":"4292816409d02b99378f31dd79e9b347","_rev":"1-88a1fdab4ca60892f3143ba09cdf6205","color":"yellow","doc_type":"Fruit","name":"Banana"}
Alternatively use CouchDB's dashboard Futon that might be accessible at
http://localhost:5984/_utils/database.html?orange_test depending on your CouchDB configuration.
We have to adapt our model test a little bit for the new constellation:
class TestFruit(object):
def setup(self):
self.fruit = Fruit()
self.fruit.set_name("Papaya")
self.fruit.set_color("orange")
self.fruit.save()
def test_color(self):
nt.assert_equal(self.fruit.name, "Papaya")
nt.assert_equal(self.fruit.color, "orange")
def test_yumminess(self):
nt.assert_true(self.fruit.is_yummy())
def test_color_change(self):
self.fruit.become_brown()
self.fruit.save()
nt.assert_not_equal(self.fruit.color, "orange")
nt.assert_equal(self.fruit.color, "brown")
def teardown(self):
self.fruit.disappear()
self.fruit.save()
If we run the test now ...
$ ./manage.py test Creating test database 'default'... Creating table auth_permission Creating table auth_group_permissions Creating table auth_group Creating table auth_user_user_permissions Creating table auth_user_groups Creating table auth_user Creating table auth_message Creating table django_content_type Creating table django_session Creating table django_site sync `fruitsalad.fruits` in CouchDB Installing index for auth.Permission model Installing index for auth.Group_permissions model Installing index for auth.User_user_permissions model Installing index for auth.User_groups model Installing index for auth.Message model No fixtures found. nosetests --verbosity 1 ...... ---------------------------------------------------------------------- Ran 6 tests in 0.352s OK Destroying test database 'default'...
... we can see that the documents produced by it are tainting the "productive" database:
$ curl -X GET http://127.0.0.1:5984/fruits/_all_docs
{"total_rows":4,"offset":0,"rows":[
{"id":"4292816409d02b99378f31dd79e9b347","key":"4292816409d02b99378f31dd79e9b347","value":{"rev":"1-88a1fdab4ca60892f3143ba09cdf6205"}},
{"id":"c85742c0ea99bd21549a1f058e4f10e6","key":"c85742c0ea99bd21549a1f058e4f10e6","value":{"rev":"2-62250a0970ba7b627322c0a3ef889f09"}},
{"id":"de4e8d3ef236bc243f3768aa7b476ffc","key":"de4e8d3ef236bc243f3768aa7b476ffc","value":{"rev":"3-b227881c2cda9384dd26934854e31347"}},
{"id":"f8f15a0be7715ae852e75379f8d0b115","key":"f8f15a0be7715ae852e75379f8d0b115","value":{"rev":"2-62250a0970ba7b627322c0a3ef889f09"}}
]}
With every run it will become more documents. But we can circumvent this problem easily. We simply modify settings.py so that the application uses a different database if it is invoked by the test runner.
if "test" in sys.argv:
COUCHDB_DATABASES = (
('fruitsalad.fruits', 'http://127.0.0.1:5984/fruits_test'),
)
If we run the tests againg ...
$ ./manage.py test
we see that the database fruits_test is filled instead of the fruits database.
$ curl -X GET http://127.0.0.1:5984/fruits_test/_all_docs
{"total_rows":12,"offset":0,"rows":[
{"id":"008a039053c555d05a16ecf3aac1f9d0","key":"008a039053c555d05a16ecf3aac1f9d0","value":{"rev":"2-62250a0970ba7b627322c0a3ef889f09"}},
{"id":"07dcba342255cec410c78f5252dde9c3","key":"07dcba342255cec410c78f5252dde9c3","value":{"rev":"3-b227881c2cda9384dd26934854e31347"}},
{"id":"19fe1c9088dce91d618cc763675c4f66","key":"19fe1c9088dce91d618cc763675c4f66","value":{"rev":"3-b227881c2cda9384dd26934854e31347"}},
{"id":"3feb80ae5aa35730a987cf90e1de34a0","key":"3feb80ae5aa35730a987cf90e1de34a0","value":{"rev":"2-62250a0970ba7b627322c0a3ef889f09"}},
{"id":"456bc0ce470e5d333122bcc9d67b4aae","key":"456bc0ce470e5d333122bcc9d67b4aae","value":{"rev":"2-62250a0970ba7b627322c0a3ef889f09"}},
{"id":"6028d26a7bf1b6ee23c43feda9341680","key":"6028d26a7bf1b6ee23c43feda9341680","value":{"rev":"2-62250a0970ba7b627322c0a3ef889f09"}},
{"id":"69504e487a73a114757f55f256b4597b","key":"69504e487a73a114757f55f256b4597b","value":{"rev":"2-62250a0970ba7b627322c0a3ef889f09"}},
{"id":"86f5a61dee535ed60374ffa4426abcc9","key":"86f5a61dee535ed60374ffa4426abcc9","value":{"rev":"2-62250a0970ba7b627322c0a3ef889f09"}},
{"id":"8d620c6b6399e94233c06034506c4779","key":"8d620c6b6399e94233c06034506c4779","value":{"rev":"2-62250a0970ba7b627322c0a3ef889f09"}},
{"id":"96317d87c5f1f27ad68dfb702458c79d","key":"96317d87c5f1f27ad68dfb702458c79d","value":{"rev":"3-b227881c2cda9384dd26934854e31347"}},
{"id":"a8ae90da0b0e60adac137fb92a16883f","key":"a8ae90da0b0e60adac137fb92a16883f","value":{"rev":"3-b227881c2cda9384dd26934854e31347"}},
{"id":"e9f63c6460d8fad804f174ff850869ce","key":"e9f63c6460d8fad804f174ff850869ce","value":{"rev":"2-62250a0970ba7b627322c0a3ef889f09"}}
]}
The only downer is that our test database is getting bigger and bigger. So we have to clean it. To that we modify
fruits/tests.py slightly. In the top of the file we add
import fruitsalad.settings as settings from couchdbkit.client import Database
and then we chance the setup method of the testing class :
def setup(self):
# cleaning the database
db_url = settings.COUCHDB_DATABASES[0][1]
database = Database(db_url)
database.flush()
# nothing new
self.fruit = Fruit()
self.fruit.set_name("Papaya")
self.fruit.set_color("orange")
self.fruit.save()
Now before every test run the previous database content is removed. Alternatively move this cleaning part into the teardown method. The advantage of the here presented solution is that you can have a look a the data in the database if you need to.



