How to run django unit-tests on production database?
Asked Answered
M

5

26

i'm starting the TDD development attitude and am writting unit-tests for my django application. I'm aware of fixtures and know that's the way tests should be executed, but for a given test i do need to execute it on the whole database, and json fixture for 10+ million row database is not something i'd like to handle, moreover, this test is "read-only".

So the question is how are you setting up your test suites to run on the production database? I imagine it could be as easy as adding the DATABASE_NAME setting in the setUp method of certain test. But the settings.DATABASE_NAME="prod_db" results in "NameError: global name 'settings' is not defined" while running the test. Moreover, there is a risk described in http://code.djangoproject.com/ticket/11987, that you can accidentally delete a production database.

So, how is it possible, or, even better, what is best practice, to run a single test of a test suite on a production database instead of temporary one?

Cheers in advance for any opinions!

Mickens answered 29/10, 2009 at 20:50 Comment(0)
S
15

First, if you're running it on the production database, it isn't much of a "unit" test.

It's a first-class batch job and needs to be treated like a first-class production batch job.

You cam't to use the Django test command for looking at production data. It always creates an empty database which is populated from fixtures in the TestCase.

You could make your production database processing a proper management command. This has the environment all properly configured so that your command can simply use the Django ORM to process your data.

The alternative is to be sure that you configure your settings. Either use the DJANGO_SETTINGS_MODULE environment variable or use the settings.configure() function to create an environment.

You can then import the models and do the processing you want to do against the production database.

You can call it "test" if you want to, but you're looking at production data, so it's got to be treated like production application with respect to getting the settings file and using the proper ORM configuration.

Semipermeable answered 29/10, 2009 at 21:0 Comment(7)
Thanks for a great answer! I do agree that's not the best practice at all what i'm trying to achieve. Writting proper management command sounds like the right way to deal with the situations as such. Thanks for direction! I guess that's the way i'll do it, but for the sake of finding out that such thing is possible, could you please specify how to properly use settings.configure() in my case? putting "from django.conf import settings" and "settings.configure(DATABASE_NAME="prod_db")" in the unit test results in "Settings already configured" runtime error(and that's exactly what docs say)?Mickens
Even though docs say that "Altering settings at runtime" should not be done, that's the way I image this problem could be solved. BTW, Steven, i've checked out your website, and both "Python for programmers" and "Object-oriented design for programmers" become among top books in my "tor-ead-next" list. Thanks a lot for writting and sharing them!Mickens
You cannot alter Django settings at runtime. You can either specify the settings via a file or via the .configure() method. Pick one. Dynamic settings don't exist.Semipermeable
I can't figure out why you'd try both DJANGO_SETTINGS_MODULE and settings.confgure(). Just pick one. You can have multiple settings files (with different names). You can use one settings file for unit test AND these production database scans.Semipermeable
If what @Mickens is describing is a BDD test, aka function test or integration test, which is probably best done with tools like behave, then it may perfectly make sense to run it against a (copy of the) production database. Just my two cents on the discussion...Selfreliant
@Selfreliant I knew when I started my tests that brought me to this question that they were integration tests, but I was unaware of behave. Thanks for the new tool to learn. (I would love to implement my tests using behave now, but due to time constraints, they'll have to stay a django management command for now.)Lissalissak
@Lissalissak As you're using Django you may want to take a look at behave-django, github.com/behave/behave-django. It has built-in support for running tests against (copies) of existing databases.Selfreliant
M
75

In case someone googles here searching for the solution for a given problem, here is the skeleton on how to perform unit tests on django production database. Check the django docs section here, for the file/directory structure, and instructions on where to put the given code. It should go in yourapp/management/commands/newcommandname.py, and both the management and commands folders should contain empty __init__.py files which makes python treat them as valid modules.

The test suite can by run as:

$python manage.py newcommandname

And here comes the code you should put in yourapp/management/commands/newcommandname.py:

from django.core.management.base import BaseCommand
import unittest

class Command(BaseCommand):
    help = """
    If you need Arguments, please check other modules in 
    django/core/management/commands.
    """

    def handle(self, **options):
        suite = unittest.TestLoader().loadTestsFromTestCase(TestChronology)
        unittest.TextTestRunner().run(suite)


class TestChronology(unittest.TestCase):
    def setUp(self):
        print "Write your pre-test prerequisites here"

    def test_equality(self):
        """
        Tests that 1 + 1 always equals 2.
        """
        from core.models import Yourmodel
        self.failUnlessEqual(1 + 1, 2)
Mickens answered 30/10, 2009 at 9:47 Comment(2)
+1 for answering the question and for showing how to do so by creating a new management command.Fritts
NoArgsCommand is now deprecated and will be removed in Django 1.10 - now the best practice is to use BaseCommand whose default is no args. All you need to change from the above answer are: from django.core.management.base import BaseCommand ... class Command(BaseCommand) ... def handle(self, **options).Dextro
S
15

First, if you're running it on the production database, it isn't much of a "unit" test.

It's a first-class batch job and needs to be treated like a first-class production batch job.

You cam't to use the Django test command for looking at production data. It always creates an empty database which is populated from fixtures in the TestCase.

You could make your production database processing a proper management command. This has the environment all properly configured so that your command can simply use the Django ORM to process your data.

The alternative is to be sure that you configure your settings. Either use the DJANGO_SETTINGS_MODULE environment variable or use the settings.configure() function to create an environment.

You can then import the models and do the processing you want to do against the production database.

You can call it "test" if you want to, but you're looking at production data, so it's got to be treated like production application with respect to getting the settings file and using the proper ORM configuration.

Semipermeable answered 29/10, 2009 at 21:0 Comment(7)
Thanks for a great answer! I do agree that's not the best practice at all what i'm trying to achieve. Writting proper management command sounds like the right way to deal with the situations as such. Thanks for direction! I guess that's the way i'll do it, but for the sake of finding out that such thing is possible, could you please specify how to properly use settings.configure() in my case? putting "from django.conf import settings" and "settings.configure(DATABASE_NAME="prod_db")" in the unit test results in "Settings already configured" runtime error(and that's exactly what docs say)?Mickens
Even though docs say that "Altering settings at runtime" should not be done, that's the way I image this problem could be solved. BTW, Steven, i've checked out your website, and both "Python for programmers" and "Object-oriented design for programmers" become among top books in my "tor-ead-next" list. Thanks a lot for writting and sharing them!Mickens
You cannot alter Django settings at runtime. You can either specify the settings via a file or via the .configure() method. Pick one. Dynamic settings don't exist.Semipermeable
I can't figure out why you'd try both DJANGO_SETTINGS_MODULE and settings.confgure(). Just pick one. You can have multiple settings files (with different names). You can use one settings file for unit test AND these production database scans.Semipermeable
If what @Mickens is describing is a BDD test, aka function test or integration test, which is probably best done with tools like behave, then it may perfectly make sense to run it against a (copy of the) production database. Just my two cents on the discussion...Selfreliant
@Selfreliant I knew when I started my tests that brought me to this question that they were integration tests, but I was unaware of behave. Thanks for the new tool to learn. (I would love to implement my tests using behave now, but due to time constraints, they'll have to stay a django management command for now.)Lissalissak
@Lissalissak As you're using Django you may want to take a look at behave-django, github.com/behave/behave-django. It has built-in support for running tests against (copies) of existing databases.Selfreliant
M
6

This TEST_RUNNER works on Django 1.3

from django.test.simple import DjangoTestSuiteRunner as TestRunner

class DjangoTestSuiteRunner(TestRunner):
    def setup_databases(self, **kwargs):
        pass

    def teardown_databases(self, old_config, **kwargs):
        pass
Mania answered 6/6, 2014 at 7:53 Comment(1)
For Django 1.8, subclass the DiscoverRunner class (from django.test.runner import DiscoverRunner).Directive
A
3

A unittest is meant to test without any sideeffects. Though your test would be nothing that is known as unittest. If you want to do it anyway, you can use a custom test runner that is setting up the database (or in your case using the existing db).

You can set the TEST_RUNNER setting in your settings.py file. The default is locate in django.test.simple.run_tests. You can look at the source here: http://code.djangoproject.com/browser/django/trunk/django/test/simple.py

Copy and paste the code in a new file and remove the following lines from the code:

connection.creation.create_test_db(verbosity, autoclobber=not interactive)
...
connection.creation.destroy_test_db(old_name, verbosity)

This will prevent django from creating a test database and reseting the database configuration of your settings file.

Aircrew answered 30/10, 2009 at 19:39 Comment(1)
It seems that it doesn't work: Djanfo testing can not find the production database :-(Otilia
S
2

Not a good idea, but if you know what you are doing (basically breaking your production) you can check this setting:

https://docs.djangoproject.com/en/2.2/ref/settings/#test

DATABASES = {
  'default': {
     ...
     'TEST': {
        'NAME': 'your prod db'
     }
}
Seaborne answered 13/8, 2019 at 15:43 Comment(1)
this may destory your prod dbRefined

© 2022 - 2024 — McMap. All rights reserved.