是否有可能在没有设置数据库的情况下编写django单元测试?我想测试不需要db设置的业务逻辑。虽然设置数据库很快,但在某些情况下我真的不需要它。
答案 0 :(得分:105)
您可以继承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
were moved到 'django.test.runner.DiscoverRunner'
。
有关更多信息,请查看有关自定义测试运行器的official doc部分。
答案 1 :(得分:54)
通常,应用程序中的测试可分为两类
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)
此策略将确保仅为访问数据库的测试用例创建和销毁数据库,因此测试将更有效
答案 2 :(得分:26)
来自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
答案 3 :(得分:8)
我选择继承django.test.runner.DiscoverRunner
并对run_tests
方法添加一些内容。
我的第一个添加检查是否需要设置db,并且如果需要db,则允许正常的setup_databases
功能启动。如果允许teardown_databases
方法运行,我的第二次添加允许正常的setup_databases
运行。
我的代码假定任何继承自django.test.TransactionTestCase
(因此django.test.TestCase
)的TestCase都需要设置数据库。我做了这个假设,因为Django文档说:
如果您需要任何其他更复杂和重量级的Django特定功能,例如......测试或使用ORM ...那么您应该使用TransactionTestCase或TestCase。
https://docs.djangoproject.com/en/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'
现在,当只运行非数据库相关的测试时,我的测试套件运行速度提高了一个数量级! :)
答案 4 :(得分:6)
已更新:另请参阅this answer以了解如何使用第三方工具pytest
。
@Cesar是对的。意外运行./manage.py test --settings=no_db_settings
后,未指定应用名称,我的开发数据库就被清除了。
为了更安全,请使用相同的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
如果您使用的是South,请运行以下命令:
./manage.py migrate --settings=mysite.no_db_settings
OK!
您现在可以通过以下方式快速(和安全)地运行单元测试:
./manage.py test myapp --settings=mysite.no_db_settings
答案 5 :(得分:2)
作为修改设置以使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
答案 6 :(得分:2)
另一个解决方案是让你的测试类只是继承自unittest.TestCase
而不是任何Django的测试类。 Django文档(https://docs.djangoproject.com/en/2.0/topics/testing/overview/#writing-tests)包含以下警告:
使用unittest.TestCase可以避免在事务中运行每个测试并刷新数据库的成本,但如果测试与数据库交互,则其行为将根据测试运行器执行它们的顺序而有所不同。这可能导致单独测试在隔离运行时通过,但在套件中运行时会失败。
但是,如果您的测试不使用数据库,则此警告无需关注您,您可以获得不必在事务中运行每个测试用例的好处。
答案 7 :(得分:0)
上述解决方案也很好。但是,如果有更多迁移,以下解决方案还将减少数据库创建时间。 在单元测试期间,运行syncdb而不是运行所有南迁移将会快得多。
SOUTH_TESTS_MIGRATE = False#禁用迁移并使用syncdb 代替
答案 8 :(得分:0)
我的网络主机只允许从他们的Web GUI创建和删除数据库,所以我得到了一个错误,创建了测试数据库:权限被拒绝"尝试运行python manage.py test
时出错。
我希望使用-keepdb选项来使用django-admin.py,但是从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的本地副本,所以至少我可以控制何时/如果我升级到更新版本版本
答案 9 :(得分:0)
另一个未提及的解决方案:这对我来说很容易实现,因为我已经有多个设置文件(用于本地/分段/生产),这些文件是从base.py继承的。因此,与其他人不同,我无需覆盖DATABASES ['default'],因为DATABASES不在base.py中设置
SimpleTestCase仍尝试连接到我的测试数据库并运行迁移。当我创建一个config / settings / test.py文件时,它没有将DATABASES设置为任何东西,然后我的单元测试就没有了。它允许我使用具有外键和唯一约束字段的模型。 (需要进行数据库查找的反向外键查找失败。)
(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
答案 10 :(得分:0)
使用鼻子测试跑步机(django-nose)时,您可以执行以下操作:
.ind[data-id="2"]{background-color:blue;}
:
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
OR
我只希望它用于运行特定的测试,所以我这样运行它:
TEST_RUNNER = 'lib.nodb_test_runner.NoDbTestRunner' . # Was 'django_nose.NoseTestSuiteRunner'
答案 11 :(得分:0)
您可以在django.test的普通TestCase中将数据库设置为空列表。
from django.test import TestCase
class NoDbTestCase(TestCase):
databases = []