Django的unit testing没有数据库

有没有可能写django unittests而不build立一个数据库? 我想testing不需要db设置的业务逻辑。 虽然build立一个数据库很快,但在某些情况下我并不需要它。

您可以inheritanceDjangoTestSuiteRunner并覆盖setup_databases和teardown_databases方法来传递。

创build一个新的设置文件,并将TEST_RUNNER设置为刚刚创build的新类。 然后,当您运行testing时,使用–settings标志指定新的设置文件。

这是我做的:

创build一个类似于以下的自定义testing服:

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 

创build一个自定义设置:

 from mysite.settings import * # Test runner with no database creation TEST_RUNNER = 'mysite.scripts.testrunner.NoDbTestRunner' 

当你运行你的testing时,像下面一样运行它–settings标志设置为新的设置文件:

 python manage.py test myapp --settings='no_db_settings' 

通常,应用程序中的testing可以分为两类

  1. unit testing,这些testing在日晒的代码片段,并不需要去数据库
  2. 集成testing用例实际上进入数据库并testing完全集成的逻辑。

Django支持unit testing和集成testing。

unit testing,不需要设置和拆除数据库,这些我们应该从SimpleTestCaseinheritance。

 from django.test import SimpleTestCase class ExampleUnitTest(SimpleTestCase): def test_something_works(self): self.assertTrue(True) 

对于从TestCaseinheritance而来的集成testing用例inheritance自TransactionTestCase,它将在运行每个testing之前设置并拆除数据库。

 from django.test import TestCase class ExampleIntegrationTest(TestCase): def test_something_works(self): #do something with database self.assertTrue(True) 

这个策略将确保仅在访问数据库的testing用例中创build和销毁数据库,因此testing将更有效率

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 

我select从django.test.runner.DiscoverRunnerinheritance,并对run_tests方法进行一些添加。

我的第一个加法检查是否需要设置一个数据库,并允许正常的setup_databasesfunction踢,如果一个数据库是必要的。 如果允许setup_databases方法运行,我的第二个添加允许正常的teardown_databases运行。

我的代码假设从django.test.TransactionTestCase (因此django.test.TestCase )inheritance的任何TestCase需要安装一个数据库。 我做了这个假设,因为Django文档说:

如果您需要其他更复杂和重量级的Django特定function,例如…testing或使用ORM …则应使用TransactionTestCase或TestCase。

https://docs.djangoproject.com/en/1.6/topics/testing/tools/#django.test.SimpleTestCase

mysite的/脚本/ settings.py

 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文件中。

mysite的/ settings.py

 TEST_RUNNER = 'mysite.scripts.settings.MyDiscoverRunner' 

现在,只运行非db相关的testing时,我的testing套件运行速度要快一个数量级! 🙂

更新:也看到使用第三方工具pytest 这个答案 。


@塞萨尔是对的。 意外运行./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' 

您需要使用外部数据库工具创build名为_test_mysite_db的数据库。 然后运行以下命令来创build相应的表格:

 ./manage.py syncdb --settings=mysite.no_db_settings 

如果您使用的是南,也运行以下命令:

 ./manage.py migrate --settings=mysite.no_db_settings 

好!

您现在可以通过以下方式快速(安全地)运行unit testing:

 ./manage.py test myapp --settings=mysite.no_db_settings 

作为修改设置以使NoDbTestRunner“安全”的替代方法,下面是NoDbTestRunner的修改版本,它closures当前的数据库连接,并从设置和连接对象中删除连接信息。 为我工作,在依靠它之前在你的环境中testing它:)

 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 

上述解决scheme也很好。 但是如果有更多的迁移次数,下面的解决scheme也会减less数据库的创build时间。 在unit testing期间,运行syncdb而不是运行所有的南迁移将会快得多。

SOUTH_TESTS_MIGRATE = False#取消迁移并使用syncdb

我的networking主机只允许从他们的Web GUI创build和删除数据库,所以当试图运行python manage.py test时,出现“创buildtesting数据库时出现错误:Permission denied”错误。

我希望将–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 ...行,并将其replace为pass所以try块不会为空。

对于_destroy_test_db我刚刚注释掉了cursor.execute("DROP DATABASE – 我不需要用任何东西replace它,因为块中已经有另一个命令了( time.sleep(1) )。

之后,我的testing运行良好 – 虽然我做了一个test_版本的常规数据库分开。

当然,这不是一个好的解决scheme,因为如果Django升级,它会中断,但是由于使用了virtualenv,所以我有了一个Django的本地副本,所以至less我可以控制何时升级到更新的版本。