4

Using DJango/Python 3.7. I read here -- How do I run all Python unit tests in a directory? that I could use a "discover" command to find tests in a specified directory. I want to have a "tests" folder, so I cretaed one and then ran

(venv) localhost:myproject davea$ python -m unittest discover tests
Traceback (most recent call last):
  File "/usr/local/Cellar/python/3.7.2_2/Frameworks/Python.framework/Versions/3.7/lib/python3.7/runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "/usr/local/Cellar/python/3.7.2_2/Frameworks/Python.framework/Versions/3.7/lib/python3.7/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/usr/local/Cellar/python/3.7.2_2/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/__main__.py", line 18, in <module>
    main(module=None)
  File "/usr/local/Cellar/python/3.7.2_2/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/main.py", line 100, in __init__
    self.parseArgs(argv)
  File "/usr/local/Cellar/python/3.7.2_2/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/main.py", line 124, in parseArgs
    self._do_discovery(argv[2:])
  File "/usr/local/Cellar/python/3.7.2_2/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/main.py", line 244, in _do_discovery
    self.createTests(from_discovery=True, Loader=Loader)
  File "/usr/local/Cellar/python/3.7.2_2/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/main.py", line 154, in createTests
    self.test = loader.discover(self.start, self.pattern, self.top)
  File "/usr/local/Cellar/python/3.7.2_2/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/loader.py", line 344, in discover
    raise ImportError('Start directory is not importable: %r' % start_dir)
ImportError: Start directory is not importable: 'tests'

This is odd to me because I have an (empty) init file ...

(venv) localhost:myproject davea$ ls web/tests/
__init__.py  model_tests.py

What else do I need to do to get my test directory recognized?

Edit: Below are the contents of model_tests.py ...

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


def setup():
    print("setup")
    management.call_command('loaddata', 'test_data.yaml', verbosity=0)


def teardown():
    management.call_command('flush', verbosity=0, interactive=False)


class ModelTest(TestCase):

    # Verify we can correctly calculate the amount of taxes when we are working
    # with a state whose tax rates are defined in our test data
    def test_calculate_tax_rate_for_defined_state(self):
        state = "MN"
        income = 30000
        taxes = IndividualTaxBracket.objects.get_taxes_owed(state, income)
        print(taxes)
        self.assertTrue(taxes > 0, "Failed to calucate taxes owed properly.")
Dave
  • 15,639
  • 133
  • 442
  • 830
  • 1
    Your `tests` directory is in the `web` directory, so you might want to *cd* into that before running your command. – Moses Koledoye Apr 12 '19 at 20:28
  • Did you forget an empty "tests.py" file as a sibling of "tests" folder ? – Mario Orlandi Apr 15 '19 at 07:57
  • @MarioOrlandi, I did have a "tests.py" file alongside "tests", which was causing some errors, so I went ahead and removed that. However I'm still unable to run the tests or target only the ones in model_tests.py from running. – Dave Apr 15 '19 at 15:04
  • Dave, a couple of questions are in order ... I'll add an answer for better text formatting ... – Mario Orlandi Apr 15 '19 at 15:33

4 Answers4

4

I think you are having some confusion about discover command. According to docs.

Unittest supports simple test discovery. In order to be compatible with test discovery, all of the test files must be modules or packages (including namespace packages) importable from the top-level directory of the project (this means that their filenames must be valid identifiers).

It means all the test files must be importable from the directory from which you are running the command (directory that holds your web directory). It make this sure, all test files must be in valid python packages (directories containing __init__.py).

Secondly you are running the command python -m unittest discover tests which is wrong. You don't have to add tests at the end. unittests with discover command support 4 options. You can read more about it here.

I have following directory structure.

 web
    ├── __init__.py
    └── tests
        ├── __init__.py
        └── test_models.py

And I am running following command.

python3 -m unittest discover

With following results.

...
----------------------------------------------------------------------
Ran 3 tests in 0.000s

OK
Nafees Anwar
  • 6,324
  • 2
  • 23
  • 42
  • So I ran "python -m unittest discover" and got the message "Ran 0 tests in 0.000s". What do I need to do/add so that the tests in my model_tests.py file will actually run? – Dave Apr 15 '19 at 15:03
  • 1
    @Dave python actually discovers tests only from specific files. pattern used for this discovery is `test*.py` it means your test files should start from `test` word. Like `test_models.py` or `test_views.py`. You can override this pattern using -p flag. like if you prefer test at the end of file name like `model_tests.py` you can do. `python -m unittest discover -p "*_tests.py"`. – Nafees Anwar Apr 15 '19 at 17:43
  • I chnaged the names of my tests to what you recommended, but now I get the error, "django.core.exceptions.ImproperlyConfigured: Requested settings, but settings are not configured". I don't know if this is related to what you have, so I opened a separate question -- https://stackoverflow.com/questions/55696337/getting-requested-settings-but-settings-are-not-configured-error-when-running . – Dave Apr 15 '19 at 19:52
  • 1
    Ah! So you are trying to run django tests with python? You cannot do that very easily. I am surprised why are you not using django's built-in workflow for running tests? However I suggest you do. `from django.conf import settings` and then `settings.configure()` in `__init__.py` file of your tests package. But I suspect some other error will pop up. I highly recommend you to use django's built-in testing setup. – Nafees Anwar Apr 16 '19 at 05:59
  • Oh, Django's way of running tests doesn't involve the "manage.py" file? I get confused with all these tutorials. – Dave Apr 16 '19 at 15:25
  • @Dave Yes it does involve `manage.py` file but there are ways around. I don't know what type of project you are working exactly on. But the most accurate documentation about testing in django is the official one. You can read it [here](https://docs.djangoproject.com/en/2.2/topics/testing/). – Nafees Anwar Apr 16 '19 at 17:39
  • @Dave You should be using `./manage.py test` for running your Django's tests. That command uses `discover()` in the back-end for collect tests. – Raydel Miranda Apr 16 '19 at 18:21
  • Ok so now that I've re-read your description of "discover" and the way Django runs tests I was clearly confused about when the unittest-discover command should be used. Running tests the normal way does solve things. – Dave Apr 16 '19 at 22:28
0

First things first: Having an __init__.py is not unusual, because the __init__.py tells python that the directory is a module; Its usual to have an empty __init__.py file. I had the same error, and fixed it by renaming my directory ..

xilpex
  • 3,097
  • 2
  • 14
  • 45
0

Should a file named tests.py exist as a sibling of tests module, that would probably cause the mentioned ImportError, and removing test.py should fix it.

If still unit tests are not discovered, a couple of question are in order:

1) does the test module contain at least a class derived from django.test.TestCase ?

2) and in that case, does that class contain at least one method whose name starts with "test_"

Please note that the name of any file containing a unit test should start with "test".

So model_test.py will not work; is is generally used to setup some fake Models, but unit tests should reside elsewhere.

You can discover and run tests with this management command:

python manage.py test

or

python manage.py test appname

Is there any particular reason for using python -m unittest discover instead ? I think that could work either, but then you'll have to manually bootstrap the django environment

Mario Orlandi
  • 5,629
  • 26
  • 29
  • I edited my question to include my model_tests.py file. The class line looks like "class ModelTest(TestCase)", so I "think" it's inheriting from what you specified but maybe there's something else going on I don't know about. – Dave Apr 15 '19 at 16:53
  • @Dave i think you need to rename model_tests.py to have any chance to discover unit tests inside it .. anything starting with "test" will do. Also, move setup() into a class method named setUp(self), and teardown() into a method called tearDown(self). I elaborated my answer with some more hints. Good luck ;) – Mario Orlandi Apr 16 '19 at 05:36
0

For completion ...

You already know that form here:

The names of your tests and files have to match a specific pattern in order to be discoverable by discover().

But then you got this error:

"django.core.exceptions.ImproperlyConfigured: Requested settings, but settings are not configured"

That means Django wasn't able to find its settings while running your tests. You can tell where to find settings using an environment variable:

DJANGO_SETTINGS_MODULE='myproyect.settings' python3 -m unittest discover

Reference: https://docs.djangoproject.com/en/2.2/topics/settings/#designating-the-settings

On the other hand ...

You should be running your Django tests with

./manage.py tests

this will search tests automatically using the same mechanism than discover(), and since you would be running a Django command, you will have some benefits against running the Django tests directly.


@Nafees Anwar asked: How does setting environment variable configure settings?

At the very beginning of the model_tests.py file there is the line from django.conf import settings, while creating the settings LazyObject instance, Django will search for that environment variable. Read the code for more detail.

I'll post here a snippet from that code for illustration.

# django.conf module.
ENVIRONMENT_VARIABLE = "DJANGO_SETTINGS_MODULE"


class LazySettings(LazyObject):
    """
    A lazy proxy for either global Django settings or a custom settings object.
    The user can manually configure settings prior to using them. Otherwise,
    Django uses the settings module pointed to by DJANGO_SETTINGS_MODULE.
    """
    def _setup(self, name=None):
        """
        Load the settings module pointed to by the environment variable. This
        is used the first time we need any settings at all, if the user has not
        previously configured the settings manually.
        """
        settings_module = os.environ.get(ENVIRONMENT_VARIABLE)
        if not settings_module:
            desc = ("setting %s" % name) if name else "settings"
            raise ImproperlyConfigured(
                "Requested %s, but settings are not configured. "
                "You must either define the environment variable %s "
                "or call settings.configure() before accessing settings."
                % (desc, ENVIRONMENT_VARIABLE))

        self._wrapped = Settings(settings_module)

So if you do:

from django.conf import settings

having that environment variable settled, the statement

settings.configure()

will fail with RuntimeError('Settings already configured.')

Raydel Miranda
  • 13,825
  • 3
  • 38
  • 60
  • How does setting environment variable [configure's settings](https://docs.djangoproject.com/en/2.2/topics/settings/#using-settings-without-setting-django-settings-module)? – Nafees Anwar Apr 16 '19 at 17:41
  • At the very beginning of the `model_tests.py` file there is the line `from django.conf import settings`, while creating the `settings` LazyObject instance, django will search for that environment variable. Read the code for more detail. – Raydel Miranda Apr 16 '19 at 17:50