django unit tests without a db

前端 未结 11 1461
醉梦人生
醉梦人生 2020-11-29 16:11

Is there a possibility to write django unittests without setting up a db? I want to test business logic which doesn\'t require the db to set up. And while it is fast to setu

相关标签:
11条回答
  • 2020-11-29 16:53

    When using the nose test runner (django-nose), you can do something like this:

    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
    

    In your settings.py you can specify the test runner there, i.e.

    TEST_RUNNER = 'lib.nodb_test_runner.NoDbTestRunner' . # Was 'django_nose.NoseTestSuiteRunner'

    OR

    I wanted it for running specific tests only, so I run it like so:

    python manage.py test integration_tests/integration_*  --noinput --testrunner=lib.nodb_test_runner.NoDbTestRunner
    
    0 讨论(0)
  • 2020-11-29 16:55

    As an alternative to modifying your settings to make NoDbTestRunner "safe", here's a modified version of NoDbTestRunner that closes the current database connection and removes the connection information from settings and the connection object. Works for me, test it in your environment before relying on it :)

    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
    
    0 讨论(0)
  • 2020-11-29 17:00

    Another solution not mentioned: this was easy for me to implement because I already have multiple settings files (for local / staging / production) that inherit from base.py . So unlike other people I did not have to overwrite DATABASES['default'], as DATABASES isn't set in base.py

    SimpleTestCase still tried to connect to my test database and run migrations. When I made a config/settings/test.py file that didn't set DATABASES to anything, then my unit tests ran without it. It allowed me to use models that had foreign key and unique constraint fields. (Reverse foreign key lookup, which requires a db lookup, fails.)

    (Django 2.0.6)

    PS code snippets

    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
    
    0 讨论(0)
  • 2020-11-29 17:01

    Generally tests in an application can be classified in to two categories

    1. Unit tests, these test the individual snippets of code in insolation and do not require to go to the database
    2. Integration test cases which actually go to the database and test the fully integrated logic.

    Django supports both unit and integration tests.

    Unit tests, do not require to setup and tear down database and these we should inherit from SimpleTestCase.

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

    For integration test cases inherit from TestCase in turn inherits from TransactionTestCase and it will setup and tear down the database before running each test.

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

    This strategy will ensure that database in created and destroyed only for the test cases that access the database and therefore tests will be more efficient

    0 讨论(0)
  • 2020-11-29 17:02

    Another solution would be to have your test class simply inherit from unittest.TestCase instead of any of Django's test classes. The Django docs (https://docs.djangoproject.com/en/2.0/topics/testing/overview/#writing-tests) contain the following warning about this:

    Using unittest.TestCase avoids the cost of running each test in a transaction and flushing the database, but if your tests interact with the database their behavior will vary based on the order that the test runner executes them. This can lead to unit tests that pass when run in isolation but fail when run in a suite.

    However, if your test doesn't use the database, this warning needn't concern you and you can reap the benefits of not having to run each test case in a transaction.

    0 讨论(0)
提交回复
热议问题