没有db的django单元测试

没有db的django单元测试,django,testing,Django,Testing,是否有可能在不设置db的情况下编写django单元测试?我想测试不需要设置db的业务逻辑。虽然设置数据库很快,但在某些情况下我确实不需要它。您可以将DjangTestSuiteRunner子类化,并覆盖要传递的设置数据库和拆卸数据库方法 创建一个新的设置文件,并将TEST_RUNNER设置为刚创建的新类。然后在运行测试时,使用--settings标志指定新的设置文件 以下是我所做的: 创建一个类似以下内容的自定义测试套装跑步者: from django.test.simple import Dj

是否有可能在不设置db的情况下编写django单元测试?我想测试不需要设置db的业务逻辑。虽然设置数据库很快,但在某些情况下我确实不需要它。

您可以将DjangTestSuiteRunner子类化,并覆盖要传递的设置数据库和拆卸数据库方法

创建一个新的设置文件,并将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'


有关更多信息,请查看有关自定义测试运行程序的部分。

更新:另请参阅有关使用第三方工具的信息
pytest


@塞萨尔是对的。在意外运行了
/manage.py test--settings=no\u db\u settings
,但没有指定应用程序名称后,我的开发数据库被清除

为了更安全,请使用相同的
nodbestrunner
,但与以下
mysite/no\u db\u 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'
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
您需要使用外部数据库工具创建一个名为
\u test\u mysite\u db
的数据库。然后运行以下命令创建相应的表:

./manage.py syncdb --settings=mysite.no_db_settings
如果使用的是South,请同时运行以下命令:

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

您现在可以通过以下方式快速(安全)运行单元测试:


作为修改设置以使NodeBtestRunner“安全”的替代方法,下面是NodeBtestRunner的修改版本,它关闭当前数据库连接并从设置和连接对象中删除连接信息。适用于我,在依赖它之前在您的环境中进行测试:)


通常,应用程序中的测试可分为两类

  • 单元测试,这些测试在Solidation中测试单个代码片段,不需要转到数据库
  • 集成测试用例,它们实际进入数据库并测试完全集成的逻辑
  • Django支持单元测试和集成测试

    单元测试,不需要设置和分解数据库,我们应该继承这些

    对于集成,测试用例继承自TestCase,然后继承自TransactionTestCase,它将在运行每个测试之前设置并拆除数据库

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

    此策略将确保仅为访问数据库的测试用例创建和销毁数据库,因此测试将更加高效

      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
    

    上述解决方案也很好。但是,如果迁移次数更多,下面的解决方案也将减少数据库创建时间。 在单元测试期间,运行syncdb而不是运行所有的南方迁移将快得多

    SOUTH_TESTS_MIGRATE=False#禁用迁移并使用syncdb 反而


    我选择继承自
    django.test.runner.DiscoverRunner
    ,并对
    run\u tests
    方法进行了一些添加

    我第一次添加的内容是检查是否需要设置数据库,并允许在需要数据库时启动正常的
    setup\u databases
    功能。如果允许运行
    setup\u databases
    方法,我的第二次添加允许正常的
    teardown\u databases
    运行

    我的代码假设从
    django.test.TransactionTestCase
    (因此
    django.test.TestCase
    )继承的任何测试用例都需要设置数据库。我做出这个假设是因为Django文档说:

    如果您需要任何其他更复杂和重量级的Django特定功能,如。。。测试或使用ORM。。。然后应该使用TransactionTestCase或TestCase

    mysite/scripts/settings.py 最后,我在项目的settings.py文件中添加了以下行

    mysite/settings.py
    现在,当只运行不依赖于db的测试时,我的测试套件运行速度要快一个数量级

    我的web主机只允许从他们的web GUI创建和删除数据库,因此我在尝试运行
    python manage.py测试时遇到了“创建测试数据库时出错:权限被拒绝”错误

    我本来希望对django-admin.py使用--keepdb选项,但从django 1.7开始,它似乎不再受支持

    最后我修改了…/Django/db/backends/creation.py中的Django代码,特别是_create\u test\u db和_destroy\u test\u db函数

    对于
    \u create\u test\u db
    我注释掉了
    光标。execute(“create DATABASE…
    行)并将其替换为
    pass
    ,这样
    try
    块就不会为空

    对于
    \u destroy\u test\u db
    我刚刚注释掉了
    cursor.execute(“DROP DATABASE
    -我不需要用任何东西替换它,因为块中已经有另一个命令(
    time.sleep(1)

    在那之后,我的测试运行良好——尽管我确实单独设置了常规数据库的测试版本


    这当然不是一个很好的解决方案,因为如果Django升级,它会崩溃,但由于使用virtualenv,我有一个Django的本地副本,所以至少我可以控制何时/是否升级到新版本。

    另一个解决方案是让您的测试类只从
    unittest.TestCase
    继承,而不是从Django的任何一个测试用例继承Django文档()包含以下警告
     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
    
    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)
    
    TEST_RUNNER = 'mysite.scripts.settings.MyDiscoverRunner'
    
    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
    
    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
    
    python manage.py test integration_tests/integration_*  --noinput --testrunner=lib.nodb_test_runner.NoDbTestRunner