views:

55

answers:

4

I'm writing unit tests in Python for the first time, for a Django app. I've struck a problem. In order to test a particular piece of functionality, I need to change the value of one of the app's settings. Here's my first attempt:

def test_in_list(self):
    mango.settings.META_LISTS = ('tags',)
    tags = Document(filepath).meta['tags']
    self.assertEqual(tags, [u'Markdown', u'Django', u'Mango'])

What I'm trying to do is change the value of META_LISTS such that the new value is used when the Document object is created. The relevant imports are...

# tests.py
from mango.models import Document
import mango.settings

# models.py
from mango.settings import *

If I've understood correctly, since models.py has already imported the names from mango.settings, changing the value of META_LISTS within mango.settings will not alter the value of META_LISTS within mango.models.

It's possible – likely even – that I'm going about this in completely the wrong way. What's the correct way to alter the value of such a "setting" from within a test case?

Edit: I failed to mention that the file models.py contains vanilla Python classes rather than Django models. I certainly need to rename this file!

+1  A: 

In models.py, use import mango.settings. You can then set a variable in your test code like you would any other:

mango.settings.foo = 'bar'

A module is a singleton. You can change the values in its namespace from anywhere in your code.

But this won't work if you use from mango.settings import *, since that expression copies the values in the module into the current namespace.

AndrewF
Thanks, Andrew, I thought this might be the case. As it stands, `models.py` references these settings in dozens of places, and I'm keen to keep the import statement as is for the sake of readability (`MARKDOWN_EXTENSIONS` is easier on the eye than `mango.settings.MARKDOWN_EXTENSIONS`). It's a good answer, though, and I like your explanation as to the functional difference between `import module` and `from module import *`.
davidchambers
You can also use `from mango import settings`, which gives you the best of both worlds.
AndrewF
Yes, although in this particular case I'm also doing `from django.conf import settings`, so "settings" is already taken.
davidchambers
What about `as`? `from mongo import settings as m_settings`
AndrewF
That works, but if one's going to use `m_settings` why not incur the extra four characters and use `mango.settings`? It's just a matter of aesthetics, really. In the end I'll probably bite the bullet and change the `import` statement, but I'm still keen to learn whether alternatives exist (i.e. whether it's possible to achieve the same result while retaining the `import *` statement).
davidchambers
+1  A: 

Will this setting be used throughout the tests? In that case one solution would be to create a settings file for testing. For e.g. add a settings_for_tests.py.

# settings_for_tests.py
from settings import * # Get everything from default settings file.

# Override just what is required.
META_LISTS = ('tags',)

And then run your tests thus:

$ python ./manage.py test mango --settings=settings_for_tests

This will ensure that the models in the test database get created using the test settings and not the default settings.

If you are doing this it also makes sense to move the settings files inside a directory. For e.g.

project
  |
  |_ settings
  |    |
  |    |_ __init__.py # Contains merely from settings import *
  |    |_ settings.py
  |    |_ settings_for_tests.py
  |
  |_ apps
       |
Manoj Govindan
Thanks very much for your answer, Manoj. I've realized that I should have been a little clearer in a couple of respects. First, the classes defined in `models.py` are not actually Django models (and don't interact with a database in any way). Perhaps there's a better name for this file? Secondly, I need to fiddle with settings throughout the test suite. For example, the following test case needs to set a different value for `META_LISTS`. As a result, the test settings could not reside in a single file.
davidchambers
@davidchambers: (1) don't add critical information in a comment. **update** the question to be **Complete**. (2) "First, the classes defined in models.py are not actually Django models" What are you doing? That's crazy. You are doomed to an epic level of confusion if you don't use Django the way it's supposed to be used. Please **update** your question to indicate that you're creating problems for yourself.
S.Lott
@S.Lott: Thanks. I've updated the question. I'm keen to address the confusion regarding the file's name, although I'm unsure as to the conventional name for such a file. Perhaps the name doesn't matter, so long as it doesn't have any special significance – heck, even `xyz.py` would be an improvement!
davidchambers
+1  A: 

There's a simpler way to do this.

Use multiple settings files -- each under proper configuration control.

We do this.

  1. We have a master settings module that has the "applies always" settings. Middleware, installed applications, other settings unique to our applications.

  2. We have "subclass" settings which (a) import the master settings and then (b) introduce platform-specific (or stage-specific, or customer-specific) settings. This is where our Windows file path stuff is isolated. Plus the locations of the static media files. Plus customer-specific template paths, etc.

  3. We break our test scripts into several parts. The "default' tests.py does basic model, form and view-function tests that must work on all platforms, all development stages (dev, test, qa, etc.) and all customers.

  4. We have separate unit test scripts which require special settings for particularly complex fixtures. These are not in tests.py and don't run automatically. They require An explicit call to Django's utilities to setup and teardown test environments.

See http://docs.djangoproject.com/en/1.2/topics/testing/#module-django.test.utils


how you would recommend testing a silly function which returns "hello" when a particular setting is truthy, and "goodbye" when falsy

This may be an indication of a poor design. Test-Driven Design (TDD) suggests that you should have designed this so that it's testable without an elaborate, complex setup.

If you must do it through settings, what are you really tesing? That the settings values propagate into your code? That's silly. You should trust that the framework works. Indeed, you have to assume the framework works, otherwise, you're obligated to test every feature of the framework.

You should have a function which accepts settings.SOME_SETTING as an argument so that you can test it as a stand-alone unit without fussing around to get the entire environment correct. You have to trust that the environment and framework actually work.

S.Lott
These sound like very sensible practices. I'm curious to know, though, how you would recommend testing a silly function which returns `"hello"` when a particular setting is truthy, and `"goodbye"` when falsy? The test cases I need to write are essentially variations on this theme. In this case the expected output differs depending on whether `'tags'` is in `META_LISTS`. There will be many similar test cases relating to different (unrelated) settings, though, so using multiple settings files could get unwieldy.
davidchambers
@davidchambers. I'll repeat the advice. "We break our test scripts into several parts". If you have tests which depend on the settings, then, you have several copies of the test in several files, each of which requires separate settings. At some point, testing the Django settings is a bit silly -- you have to trust something, and trusting your framework is essential.
S.Lott
+1  A: 

For changing settings in TestCases i use modified version of this snippet http://www.djangosnippets.org/snippets/1011/

Here is my modification of this snippet http://github.com/dominno/django-moderation/blob/master/src/moderation/tests/utils/testsettingsmanager.py

Then i create file with my test settings and then i use(example from my project):

class SerializationTestCase(SettingsTestCase):
    fixtures = ['test_users.json', 'test_moderation.json']
    test_settings = 'moderation.tests.settings.generic'

    def setUp(self):
        self.user = User.objects.get(username='moderator')
        self.profile = UserProfile.objects.get(user__username='moderator')

    def test_serialize_of_object(self):
        """Test if object is propertly serialized to json"""

        json_field = SerializedObjectField()

        self.assertEqual(json_field._serialize(self.profile),
                    '[{"pk": 1, "model": "test_app.userprofile", "fields": '\
                    '{"url": "http://www.google.com", "user": 1, '\
                    '"description": "Old description"}}]',
                         )

It will keep track of the original settings and let easily revert them back when test is finished.

Dominik Szopa