Django manage.py : Is it possible to pass command line argument (for unit testing)
Asked Answered
H

6

24

Is it possible to pass command line arguments to Django's manage.py script, specifically for unit tests? i.e. if I do something like

manage.py test myapp -a do_this

Can I receive the value do_this in the setUp function of unit test?

P.S. @Martin asked the justification for using command line args in tests:

  • Some extensive tests take a lot of time and don't need to be run before every commit. I want to make them optional.

  • Occasional debug messages printed by my test cases should be optional

  • Sometimes I just want the tests to go crazy and try a lot more permutations of data.

All the above would be quite convenient with command line options. Once in a while testing can be a lot more extensive or verbose, otherwise it'll be quick.

Hermilahermina answered 30/1, 2015 at 5:39 Comment(4)
Regardless of yes or no, wouldn't it make more sense to add the arguments to the unit test itself? That's really one of the main use cases of a unit test - checking different edge cases etc. If you define different test functions for each case, you'll be able to call them separately like manage.py test myapp.mytestcaseLifeblood
@Martin Fair point. I've elaborated my thoughts in the answer (P.S.)Hermilahermina
ok, then I would create functions like testBasic, testCrazy etc. and run whatever test you need on commit. I completely agree that running a whole test suite of a big project on every commit can be annoying - that's why you should create a new test for a commit or just pick the test that the commit is related to.Lifeblood
@Hermilahermina - "manage.py test myapp.mytestcase" isn't enough for me. Sometimes I want to test on a specific client-id. Then what?Sharlasharleen
I
11

I just ran into this problem myself, and I wanted to avoid setting environmental variables on the command line. Environmental variables certainly work, but it's difficult to keep track of which variables have an effect and there's no error message to let you know if you've mistyped one of them.

To get around this I've used argparse to extract extra parameters to the command-line argument. For example, my manage.py file now looks something like this:

#!/usr/bin/env python
import os
import sys
import argparse


if __name__ == "__main__":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings")

    argv = sys.argv
    cmd = argv[1] if len(argv) > 1 else None
    if cmd in ['test']:  # limit the extra arguments to certain commands
        parser = argparse.ArgumentParser(add_help=False)
        parser.add_argument('--foo', default='bar')
        args, argv = parser.parse_known_args(argv)
        # We can save the argument as an environmental variable, in
        # which case it's to retrieve from within `project.settings`,
        os.environ['FOO'] = args.foo
        # or we can save the variable to settings directly if it
        # won't otherwise be overridden.
        from django.conf import settings
        settings.foo = args.foo

    from django.core.management import execute_from_command_line

    # parse_known_args strips the extra arguments from argv,
    # so we can safely pass it to Django.
    execute_from_command_line(argv)

argparse is a really nice library with lots of features. There's a good tutorial on it in the Python docs.

Intermingle answered 9/5, 2017 at 19:56 Comment(0)
S
10

i'm using environment variables workaround in my project (works in unix-like shells only)

berry$ myvar=myval ./manage.py test 

in your module read this value using

os.environ.get('myvar')
Sharlasharleen answered 6/3, 2016 at 0:41 Comment(0)
B
7

Django allows adding custom commandline options from the testrunner class. You can create a subclass of the default testrunner class and add your own options, and then let django use your custom testrunner as follows.

For example, create a testrunner.py in your Django project directory, containing:

from django.test.runner import DiscoverRunner

class TestRunner(DiscoverRunner):
    def __init__(self, option=None, **kwargs):
        super().__init__(**kwargs)

        print("Passed option: {}".format(option))

    @classmethod
    def add_arguments(cls, parser):
        DiscoverRunner.add_arguments(parser)

        parser.add_argument('-o', '--option', help='Example option')

This is a testrunner that derives from the default runner (so it works just like the default), except that it tells django to add an extra commandline option (in the add_arguments() class method) and processes the value of this extra option in the constructor. To run with this new runner, pass its name as follows:

./manage.py test --testrunner=testrunner.TestRunner -o foo

Of course you can put this class anywhere else, as long as you pass the full import name to it on the commandline.

Note that you must use --testrunner=foo, you cannot use two separate arguments (--testrunner foo), since then the extra arguments do not work. A fix is pending: https://github.com/django/django/pull/10307

This example just prints the option value, but you'll want to pass it to your testcase somehow. I couldn't find any quick info on how to pass options to unittest testcases, but you could probably just use a global (module level) variable or class variable for this (which is not so re-entrant and elegant, but is easy and works).

Bairam answered 17/8, 2018 at 18:25 Comment(0)
O
4

As an alternative way to manage.py test -a do_this you can use specific settings file

manage.py --settings=project.test_settings test

and define in this file whatever you want.

# test_setting.py
SPECIFIC_OPTION = "test"

# tests.py
from django.conf import settings
...
def setUp(self):
    if settings.SPECIFIC_OPTION:
        ....

If you need really dynamic options, maybe you can use sys.argv in test_settings.py, but it is a really dirty hack.

Outrageous answered 30/1, 2015 at 8:7 Comment(3)
Thanks for suggesting this workaround. You mention : you can use sys.argv in test_settings.py, but it is a really dirty hack : Can you elaborate?Hermilahermina
You can pass any arguments to command line: $ manage.py --settings=test_settings test some-specific-option, and then use something like this in test_settings.py: if sys.argv[-1] == "something": # do stuff (and you must drop this extra argument after all: del sys.argv[-1])Outrageous
I do not find anything hacky about this. I have a separate settings file for my CI anyway, just put another var in it. Simple is Better than Complex. :)Calva
T
4

Following up on @Matthijs answer, you can extend the setup_test_environment method similar to how the DiscoveryRunner handles the debug-mode. It changes the value of settings.DEBUG which can be used in your tests by importing django.conf.settings. But you can also add your own settings:

from django.test.runner import DiscoverRunner

class TestRunner(DiscoverRunner):
    def __init__(self, option=None, **kwargs):
        super().__init__(**kwargs)

        print("Passed option: {}".format(option))
        self.option = option

    @classmethod
    def add_arguments(cls, parser):
        DiscoverRunner.add_arguments(parser)

        parser.add_argument('-o', '--option', help='Example option')

    def setup_test_environment(self, **kwargs):
        super(TestRunner, self).setup_test_environment(**kwargs)
        settings.TEST_SETTINGS = {
            'option': self.option,
        }

In the test you can then simply access the option via the settings

from django.test import TestCase
from django.conf import settings


class MyTestCase(TestCase):

    def test_something(self):
        if settings.TEST_SETTINGS['option']:
            print("Do stuff")
Tenacious answered 27/5, 2020 at 10:50 Comment(0)
I
0

Django has a built-in mechanism for running specific tests: tags. Use something like this:

from django.test import tag

class Foo(...):

    @tag('slow')
    def test_slow_something(...):
       ...

Execute slow tests with:

./manage.py test --tag=slow

Read more at https://docs.djangoproject.com/en/4.2/topics/testing/tools/#topics-tagging-tests

Isonomy answered 27/10, 2023 at 15:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.