On Thursday, October 4, 2012 7:49:19 PM UTC+2, Daniele Procida wrote:
I have started writing my first tests, for a project that has become pretty large (several thousand lines of source code).
What needs the most testing - where most of the bugs or incorrect
appear emerge - are the very complex interactions between objects in the
system.
To me, the intuitive way of testing would be this:
* to set up all the objects, in effect creating a complete working database
* run all the tests on this database
This method might work even if maintaining the
fixture can be extra trouble. I never used fixture for doing tests, the
useful case might not have presented itself but we had another way of
doing it.
That's pretty much the way I test things without automated tests: is the
output of the system, running a huge database of objects, correct?
Yes, but UT, should also be some kind of a
documentation of how the projects works, having a pre-built database
doesn't give much information about how to setup a correct database for
the project whereas fine grained UT do.
However, I keep reading that I should isolate all my tests.
Yes, but there might be cases where tests fixtures are useful, I don't know them.
So
I have had a go at creating tests that do that, but it can mean setting
up a dozen objects sometimes for a single tiny test, then doing exactly
the same thing with one small difference for another test.
Use factories for that. there is
factory boy
that seems to do the job, but I don't know its value, we were using our
own factory and/or build the database manually, like I said earlier
documenting/testing the way the database should look like in the tests
is IMO significant. You can also test the factory which might serve as
documentation/tests of the proper building of the database something
like:
def test_build_base_site(self):
Factory.build(Site, domain='foo', name='bar')
self.assertEquals(Site.objects.count(), 1)
site = Site.objects.all()[0]
# test domain name and name
def test_build_section(self):
Factory.build(Section, name='Test Section') # other fields are generated ! You cannot test them outsite factory UT of course...
self.assertEquals(Section.objects.all(), 1)
section = Section.objects.all()[0]
self.assertNotNone(section.site)
def test_build_article(self):
section = Factory.build('Article')
self.assertEquals(Article.objects.count(), 1)
article = Article.objects.all()[0]
self.assertNotNone(article.section)
self.assertNotNone(article.section.site)
# etc...
The factory takes some arguments to populate the fields of the objects you asked for (you probably can use magic parameters to populate related fields too), builds the object you asked for.
I don't know how complex is your schema, the factory can be recursive so it's not problem.
Often I have to run save() on these objects, because otherwise tests
that depend on many-to-many and other database relations won't work.
I don't understand what you mean.
That seems very inefficient, to create a succession of complex and
nearly-identical test conditions for dozens if not hundreds of tests.
Could you give an example of nearly identifical tests ?
I'd appreciate any advice.
I don't know how does your project setup looks like but here is the one I worked with:
For each feature (or group of features) we split it in two apps, a generic app and an integration/project app. The generic app can be re-used in other projects, whereas the integration app does the specific templates, model, signal hooking specific to the current project. Then the tests follows, in the generic apps you tests generics things (that don't need a particular project setup...) and in the integration/project app you tests everything else. Client side testing will go in integration app testing because you will most likely override templates in it and if you do client side testing in the generic app they will fail because of the overrides.
Gotchas:
- Django application template create a tests.py in the application directory, remove it and create a directory instead (or use another application template), you will most likely need several files to do the testing (factory, models, feature1, feature2) and mixing it with the application files is ugly (you can also have a tests.py with a loc > 1000). Don't forget to create tests/__init__.py and import every test classes you create so that they can be found by Django test runner
- If the generic apps needs some models, they should be defined in the tests/__init__.py (If I remember well)
- If you have A LOT of tests, or simply want to make the life of the developpers easier look for fsync=off (for PostgresSQL) or similar option in you database of choice or (prefered if possible)
use in memory sqlite.
I'm not sure but if you use fixtures and they are created between all the tests (classes) the test run speed may increase quiet a bit making it quiet -- aweful -- to test the project hence developing it, that's also one of the reasons why generic apps are a good idea because they can be developped and properly tested separatly, can improve your productivity and you can open-source the generic app ;).
Amirouche