django单元测试没有db

时间:2011-05-06 22:16:25

标签: django testing

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

12 个答案:

答案 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)

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

  1. 单元测试,这些测试在日照中测试代码的各个片段,不需要转到数据库
  2. 集成测试用例,实际进入数据库并测试完全集成的逻辑。
  3. 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

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'

现在,当只运行非数据库相关的测试时,我的测试套件运行速度提高了一个数量级! :)

答案 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 = []