是否可以在不设置数据库的情况下编写Django单元测试?我想测试不需要设置数据库的业务逻辑。尽管设置数据库的速度很快,但在某些情况下我真的不需要它。
是否可以在不设置数据库的情况下编写Django单元测试?我想测试不需要设置数据库的业务逻辑。尽管设置数据库的速度很快,但在某些情况下我真的不需要它。
Answers:
您可以继承DjangoTestSuiteRunner的子类,并覆盖setup_databases和teardown_databases方法以传递。
创建一个新的设置文件,并将TEST_RUNNER设置为刚创建的新类。然后,在运行测试时,请使用--settings标志指定新的设置文件。
这是我所做的:
创建一个类似于以下内容的自定义测试服跑步者:
from django.test.simple import DjangoTestSuiteRunner
class NoDbTestRunner(DjangoTestSuiteRunner):
""" A test runner to test without database creation """
def setup_databases(self, **kwargs):
""" Override the database creation defined in parent class """
pass
def teardown_databases(self, old_config, **kwargs):
""" Override the database teardown defined in parent class """
pass
创建自定义设置:
from mysite.settings import *
# Test runner with no database creation
TEST_RUNNER = 'mysite.scripts.testrunner.NoDbTestRunner'
在运行测试时,将--settings标志设置为新的设置文件,如下所示运行它:
python manage.py test myapp --settings='no_db_settings'
更新:2018年4月
由于Django的1.8,该模块被移动到。django.test.simple.DjangoTestSuiteRunner
'django.test.runner.DiscoverRunner'
--testrunner
选项指定新的TestRunner 。
通常,应用程序中的测试可以分为两类
Django支持单元测试和集成测试。
单元测试不需要设置和拆除数据库,而这些我们应该从SimpleTestCase继承。
from django.test import SimpleTestCase
class ExampleUnitTest(SimpleTestCase):
def test_something_works(self):
self.assertTrue(True)
对于集成测试用例,继承自TestCase的内容又继承自TransactionTestCase,它将在运行每个测试之前设置和拆除数据库。
from django.test import TestCase
class ExampleIntegrationTest(TestCase):
def test_something_works(self):
#do something with database
self.assertTrue(True)
该策略将确保仅针对访问数据库的测试用例创建和销毁数据库,因此测试将更加高效
从 django.test.simple
warnings.warn(
"The django.test.simple module and DjangoTestSuiteRunner are deprecated; "
"use django.test.runner.DiscoverRunner instead.",
RemovedInDjango18Warning)
因此,请覆盖DiscoverRunner
而不是DjangoTestSuiteRunner
。
from django.test.runner import DiscoverRunner
class NoDbTestRunner(DiscoverRunner):
""" A test runner to test without database creation/deletion """
def setup_databases(self, **kwargs):
pass
def teardown_databases(self, old_config, **kwargs):
pass
这样使用:
python manage.py test app --testrunner=app.filename.NoDbTestRunner
我选择继承自django.test.runner.DiscoverRunner
该run_tests
方法,并对其进行了一些补充。
我的第一个添加项检查是否需要设置数据库,并在需要数据库的情况setup_databases
下启动正常功能。teardown_databases
如果setup_databases
允许该方法运行,我的第二次添加使法线运行。
我的代码假定任何继承自django.test.TransactionTestCase
(因此django.test.TestCase
)的TestCase都需要设置数据库。我做这个假设是因为Django文档说:
如果您需要其他任何更复杂且重量级的Django特定功能,例如...测试或使用ORM ...,则应改用TransactionTestCase或TestCase。
https://docs.djangoproject.com/zh-CN/1.6/topics/testing/tools/#django.test.SimpleTestCase
from django.test import TransactionTestCase
from django.test.runner import DiscoverRunner
class MyDiscoverRunner(DiscoverRunner):
def run_tests(self, test_labels, extra_tests=None, **kwargs):
"""
Run the unit tests for all the test labels in the provided list.
Test labels should be dotted Python paths to test modules, test
classes, or test methods.
A list of 'extra' tests may also be provided; these tests
will be added to the test suite.
If any of the tests in the test suite inherit from
``django.test.TransactionTestCase``, databases will be setup.
Otherwise, databases will not be set up.
Returns the number of tests that failed.
"""
self.setup_test_environment()
suite = self.build_suite(test_labels, extra_tests)
# ----------------- First Addition --------------
need_databases = any(isinstance(test_case, TransactionTestCase)
for test_case in suite)
old_config = None
if need_databases:
# --------------- End First Addition ------------
old_config = self.setup_databases()
result = self.run_suite(suite)
# ----------------- Second Addition -------------
if need_databases:
# --------------- End Second Addition -----------
self.teardown_databases(old_config)
self.teardown_test_environment()
return self.suite_result(suite, result)
最后,我将以下行添加到项目的settings.py文件中。
TEST_RUNNER = 'mysite.scripts.settings.MyDiscoverRunner'
现在,当仅运行与数据库无关的测试时,我的测试套件的运行速度提高了一个数量级!:)
更新:也请参见使用第三方工具的答案pytest
。
@Cesar是正确的。意外运行后./manage.py test --settings=no_db_settings
,没有指定应用程序名称,我的开发数据库被清除了。
为了安全起见,请使用same NoDbTestRunner
,但要结合以下使用mysite/no_db_settings.py
:
from mysite.settings import *
# Test runner with no database creation
TEST_RUNNER = 'mysite.scripts.testrunner.NoDbTestRunner'
# Use an alternative database as a safeguard against accidents
DATABASES['default']['NAME'] = '_test_mysite_db'
您需要_test_mysite_db
使用外部数据库工具创建一个名为的数据库。然后运行以下命令来创建相应的表:
./manage.py syncdb --settings=mysite.no_db_settings
如果您使用的是南方,还请运行以下命令:
./manage.py migrate --settings=mysite.no_db_settings
好!
现在,您可以通过以下方法快速(安全)地运行单元测试:
./manage.py test myapp --settings=mysite.no_db_settings
作为修改设置以使NoDbTestRunner“安全”的一种替代方法,这是NoDbTestRunner的修改版本,它关闭当前数据库连接并从设置和连接对象中删除连接信息。为我工作,在依赖它之前先在您的环境中对其进行测试:)
class NoDbTestRunner(DjangoTestSuiteRunner):
""" A test runner to test without database creation """
def __init__(self, *args, **kwargs):
# hide/disconnect databases to prevent tests that
# *do* require a database which accidentally get
# run from altering your data
from django.db import connections
from django.conf import settings
connections.databases = settings.DATABASES = {}
connections._connections['default'].close()
del connections._connections['default']
super(NoDbTestRunner,self).__init__(*args,**kwargs)
def setup_databases(self, **kwargs):
""" Override the database creation defined in parent class """
pass
def teardown_databases(self, old_config, **kwargs):
""" Override the database teardown defined in parent class """
pass
__getitem__
。使用connections._connections.default
为了访问对象。
另一个解决方案是让您的测试类简单地继承unittest.TestCase
而不是Django的任何测试类。Django文档(https://docs.djangoproject.com/zh-CN/2.0/topics/testing/overview/#writing-tests)包含以下有关此的警告:
使用unittest.TestCase可以避免在事务中运行每个测试并刷新数据库的开销,但是如果您的测试与数据库进行交互,则其行为将根据测试运行者执行它们的顺序而有所不同。这可能导致单元测试在单独运行时通过,但在套件中运行时失败。
但是,如果您的测试未使用数据库,则此警告与您无关,您可以从不必在事务中运行每个测试用例的好处中获益。
我的Web主机仅允许从其Web GUI创建和删除数据库,因此在尝试运行时遇到了“创建测试数据库时出错:权限被拒绝”错误python manage.py test
。
我希望对django-admin.py使用--keepdb选项,但是从Django 1.7开始似乎不再受支持。
我最终要做的是修改... / django / db / backends / creation.py中的Django代码,特别是_create_test_db和_destroy_test_db函数。
因为_create_test_db
我注释掉了这一cursor.execute("CREATE DATABASE ...
行并将其替换为该行,pass
所以该try
块不会为空。
因为_destroy_test_db
我刚刚注释掉了cursor.execute("DROP DATABASE
-我不需要用任何东西替换它,因为代码块(time.sleep(1)
)中已经有另一个命令。
之后,我的测试运行良好-尽管我确实单独设置了常规数据库的test_版本。
当然,这不是一个很好的解决方案,因为如果升级Django,它会中断,但是由于使用virtualenv,我拥有Django的本地副本,因此至少我可以控制何时/是否升级到较新的版本。
另一个未提及的解决方案:这对我来说很容易实现,因为我已经有多个设置文件(用于本地/暂存/生产),这些文件是从base.py继承的。因此,与其他人不同,我无需覆盖DATABASES ['default'],因为未在base.py中设置DATABASES
SimpleTestCase仍尝试连接到我的测试数据库并运行迁移。当我创建一个config / settings / test.py文件时,它没有将DATABASES设置为任何东西,然后我的单元测试就没有了。它允许我使用具有外键和唯一约束字段的模型。(需要db查找的反向外键查找失败。)
(Django 2.0.6)
PS代码段
PROJECT_ROOT_DIR/config/settings/test.py:
from .base import *
#other test settings
#DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.sqlite3',
# 'NAME': 'PROJECT_ROOT_DIR/db.sqlite3',
# }
#}
cli, run from PROJECT_ROOT_DIR:
./manage.py test path.to.app.test --settings config.settings.test
path/to/app/test.py:
from django.test import SimpleTestCase
from .models import *
#^assume models.py imports User and defines Classified and UpgradePrice
class TestCaseWorkingTest(SimpleTestCase):
def test_case_working(self):
self.assertTrue(True)
def test_models_ok(self):
obj = UpgradePrice(title='test',price=1.00)
self.assertEqual(obj.title,'test')
def test_more_complex_model(self):
user = User(username='testuser',email='hi@hey.com')
self.assertEqual(user.username,'testuser')
def test_foreign_key(self):
user = User(username='testuser',email='hi@hey.com')
ad = Classified(user=user,headline='headline',body='body')
self.assertEqual(ad.user.username,'testuser')
#fails with error:
def test_reverse_foreign_key(self):
user = User(username='testuser',email='hi@hey.com')
ad = Classified(user=user,headline='headline',body='body')
print(user.classified_set.first())
self.assertTrue(True) #throws exception and never gets here
使用鼻子测试跑步机(django-nose)时,您可以执行以下操作:
my_project/lib/nodb_test_runner.py
:
from django_nose import NoseTestSuiteRunner
class NoDbTestRunner(NoseTestSuiteRunner):
"""
A test runner to test without database creation/deletion
Used for integration tests
"""
def setup_databases(self, **kwargs):
pass
def teardown_databases(self, old_config, **kwargs):
pass
在你的settings.py
,你可以在那里指定的测试运行,即
TEST_RUNNER = 'lib.nodb_test_runner.NoDbTestRunner' . # Was 'django_nose.NoseTestSuiteRunner'
要么
我只希望它用于运行特定的测试,所以我可以这样运行:
python manage.py test integration_tests/integration_* --noinput --testrunner=lib.nodb_test_runner.NoDbTestRunner