问题
Can I launch tests for my reusable Django app without incorporating this app into a project?
My app uses some models, so it is necessary to provide (TEST_)DATABASE_*
settings. Where should I store them and how should I launch tests?
For a Django project, I can run tests with manage.py test
; when I use django-admin.py test
with my standalone app, I get:
Error: Settings cannot be imported, because environment variable DJANGO_SETTINGS_MODULE is undefined.
What are the best practises here?
回答1:
The correct usage of Django (>= 1.4) test runner is as follows:
import django, sys
from django.conf import settings
settings.configure(DEBUG=True,
DATABASES={
'default': {
'ENGINE': 'django.db.backends.sqlite3',
}
},
ROOT_URLCONF='myapp.urls',
INSTALLED_APPS=('django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.admin',
'myapp',))
try:
# Django < 1.8
from django.test.simple import DjangoTestSuiteRunner
test_runner = DjangoTestSuiteRunner(verbosity=1)
except ImportError:
# Django >= 1.8
django.setup()
from django.test.runner import DiscoverRunner
test_runner = DiscoverRunner(verbosity=1)
failures = test_runner.run_tests(['myapp'])
if failures:
sys.exit(failures)
DjangoTestSuiteRunner and DiscoverRunner have mostly compatible interfaces.
For more information you should consult the "Defining a Test Runner" docs:
- DjangoTestSuiteRunner (Django >=1.4, <1.8)
- DiscoverRunner (Django >=1.8)
回答2:
I've ended with such solution (it was inspired by solution found in django-voting):
Create file eg. 'runtests.py' in tests dir containing:
import os, sys
from django.conf import settings
DIRNAME = os.path.dirname(__file__)
settings.configure(DEBUG = True,
DATABASE_ENGINE = 'sqlite3',
DATABASE_NAME = os.path.join(DIRNAME, 'database.db'),
INSTALLED_APPS = ('django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.admin',
'myapp',
'myapp.tests',))
from django.test.simple import run_tests
failures = run_tests(['myapp',], verbosity=1)
if failures:
sys.exit(failures)
It allows to run tests by python runtests.py
command.
It doesn't require installed dependencies (eg. buildout) and it doesn't harm tests run when app is incorporated into bigger project.
回答3:
For Django 1.7 it's slightly different. Assuming you have the following
directory structure for app foo
:
foo
|── docs
|── foo
│ ├── __init__.py
│ ├── models.py
│ ├── urls.py
│ └── views.py
└── tests
├── foo_models
│ ├── __init__.py
│ ├── ...
│ └── tests.py
├── foo_views
│ ├── __init__.py
│ ├── ...
│ └── tests.py
├── runtests.py
└── urls.py
This is how the Django project itself structures its tests.
You want to run all tests in foo/tests/
with the command:
python3 runtests.py
You also want to be able to run the command from a parent directory of tests
, e.g. by Tox or Invoke, just like python3 foo/tests/runtests.py
.
The solution I present here is quite reusable, only the app's name foo
must be adjusted (and additional apps, if necessary). They can not be installed via modify_settings, because it would miss the database setup.
The following files are needed:
urls.py
"""
This urlconf exists because Django expects ROOT_URLCONF to exist. URLs
should be added within the test folders, and use TestCase.urls to set them.
This helps the tests remain isolated.
"""
urlpatterns = []
runtests.py
#!/usr/bin/env python3
import glob
import os
import sys
import django
from django.conf import settings
from django.core.management import execute_from_command_line
BASE_DIR = os.path.abspath(os.path.dirname(__file__))
sys.path.append(os.path.abspath(os.path.join(BASE_DIR, '..')))
# Unfortunately, apps can not be installed via ``modify_settings``
# decorator, because it would miss the database setup.
CUSTOM_INSTALLED_APPS = (
'foo',
'django.contrib.admin',
)
ALWAYS_INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
)
ALWAYS_MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
)
settings.configure(
SECRET_KEY="django_tests_secret_key",
DEBUG=False,
TEMPLATE_DEBUG=False,
ALLOWED_HOSTS=[],
INSTALLED_APPS=ALWAYS_INSTALLED_APPS + CUSTOM_INSTALLED_APPS,
MIDDLEWARE_CLASSES=ALWAYS_MIDDLEWARE_CLASSES,
ROOT_URLCONF='tests.urls',
DATABASES={
'default': {
'ENGINE': 'django.db.backends.sqlite3',
}
},
LANGUAGE_CODE='en-us',
TIME_ZONE='UTC',
USE_I18N=True,
USE_L10N=True,
USE_TZ=True,
STATIC_URL='/static/',
# Use a fast hasher to speed up tests.
PASSWORD_HASHERS=(
'django.contrib.auth.hashers.MD5PasswordHasher',
),
FIXTURE_DIRS=glob.glob(BASE_DIR + '/' + '*/fixtures/')
)
django.setup()
args = [sys.argv[0], 'test']
# Current module (``tests``) and its submodules.
test_cases = '.'
# Allow accessing test options from the command line.
offset = 1
try:
sys.argv[1]
except IndexError:
pass
else:
option = sys.argv[1].startswith('-')
if not option:
test_cases = sys.argv[1]
offset = 2
args.append(test_cases)
# ``verbosity`` can be overwritten from command line.
args.append('--verbosity=2')
args.extend(sys.argv[offset:])
execute_from_command_line(args)
Some of the settings are optional; they improve speed or a more realistic environment.
The second argument points to the current directory. It makes use of the feature of providing a path to a directory to discover tests below that directory.
回答4:
For my reusable app(django-moderation) I use buildout. I create example_project
, i use it with buildout to run tests on it. I simply put my app inside of settings of example_project
.
When i want to install all dependencies used by my project and run tests, i only need to do following:
- Run: python bootstrap.py
Run buildout:
bin/buildout
Run tests for Django 1.1 and Django 1.2:
bin/test-1.1 bin/test-1.2
Here you can find tutorial how to configure reusable app to use buildout for deployment and tests run: http://jacobian.org/writing/django-apps-with-buildout/
Here you will find example buildout config which i use in my project:
http://github.com/dominno/django-moderation/blob/master//buildout.cfg
回答5:
In a pure pytest context, to provide just enough Django environment to get tests running for my reusable app without an actual Django project, I needed the following pieces:
pytest.ini:
[pytest]
DJANGO_SETTINGS_MODULE = test_settings
python_files = tests.py test_*.py *_tests.py
test_settings.py:
# You may need more or less than what's shown here - this is a skeleton:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
}
}
INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.messages',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.staticfiles',
'todo',
)
ROOT_URLCONF = 'base_urls'
TEMPLATES = [
{
'DIRS': ['path/to/your/templates'), ],
}
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
base_urls.py:
"""
This urlconf exists so we can run tests without an actual
Django project (Django expects ROOT_URLCONF to exist.)
It is not used by installed instances of this app.
"""
from django.urls import include, path
urlpatterns = [
path('foo/', include('myapp.urls')),
]
templates/base.html:
If any of your tests hit actual views, your app's templates will probably be extending the project base.html
, so that file must exist. In my case, I just created an empty file templates/base.html
.
I can now run pytest -x -v
from my standalone reusable app directory, without a Django project.
来源:https://stackoverflow.com/questions/3841725/how-to-launch-tests-for-django-reusable-app