kiểm tra đơn vị django mà không có db


126

Có khả năng viết django unittests mà không cần thiết lập db không? Tôi muốn kiểm tra logic nghiệp vụ không yêu cầu db thiết lập. Và mặc dù rất nhanh để thiết lập db, tôi thực sự không cần nó trong một số trường hợp.


Tôi tự hỏi nếu điều đó thực sự quan trọng. Db được giữ trong bộ nhớ + nếu bạn không có bất kỳ mô hình nào thì không có gì được thực hiện với db. Vì vậy, nếu bạn không cần, đừng thiết lập mô hình.
Torsten Engelbrecht

3
Tôi có mô hình, nhưng đối với những thử nghiệm đó, chúng không liên quan. Và db không được lưu trong bộ nhớ, nhưng được xây dựng trong mysql, tuy nhiên, đặc biệt cho mục đích này. Không phải tôi muốn điều này .. Có lẽ tôi có thể cấu hình django để sử dụng db trong bộ nhớ để thử nghiệm. Bạn có biết làm thế nào để làm điều này?
paweloque

Tôi xin lỗi. Cơ sở dữ liệu trong bộ nhớ chỉ là trường hợp khi bạn sử dụng cơ sở dữ liệu SQLite. Ngoại trừ điều này tôi không thấy cách nào để tránh tạo db thử nghiệm. Không có gì về điều này trong các tài liệu + Tôi chưa bao giờ cảm thấy cần phải tránh nó.
Torsten Engelbrecht

3
Câu trả lời được chấp nhận không làm việc cho tôi. Thay vào đó, điều này hoạt động hoàn hảo: caktusgroup.com/blog/2013/10/02/skipping-test-db-creation
Hugo Pineda

Câu trả lời:


122

Bạn có thể phân lớp DjangoTestSuiteRunner và ghi đè các phương thức setup_database và torndown_database để vượt qua.

Tạo một tệp cài đặt mới và đặt TEST_RUNNER cho lớp mới mà bạn vừa tạo. Sau đó, khi bạn đang chạy thử nghiệm, hãy chỉ định tệp cài đặt mới của bạn với cờ --sinstall.

Đây là những gì tôi đã làm:

Tạo một trình chạy thử phù hợp tùy chỉnh tương tự như sau:

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

Tạo một cài đặt tùy chỉnh:

from mysite.settings import *

# Test runner with no database creation
TEST_RUNNER = 'mysite.scripts.testrunner.NoDbTestRunner'

Khi bạn đang chạy thử nghiệm, hãy chạy nó như sau với cờ - cài đặt được đặt thành tệp cài đặt mới của bạn:

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

CẬP NHẬT: Tháng 4/2018

Kể từ Django 1.8, mô-đun đã được chuyển đến .django.test.simple.DjangoTestSuiteRunner 'django.test.runner.DiscoverRunner'

Để biết thêm thông tin kiểm tra phần tài liệu chính thức về người chạy thử nghiệm tùy chỉnh.


2
Lỗi này xuất hiện khi bạn có các bài kiểm tra cần giao dịch cơ sở dữ liệu. Rõ ràng nếu bạn không có DB, bạn sẽ không thể chạy các thử nghiệm đó. Bạn nên chạy thử nghiệm riêng của bạn. Nếu bạn chỉ chạy thử nghiệm của mình bằng cách sử dụng python Manage.txt test --sinstall = new_sinstall.py, thì nó sẽ chạy một loạt các thử nghiệm khác từ các ứng dụng khác có thể yêu cầu cơ sở dữ liệu.
mohi666

5
Lưu ý rằng bạn sẽ cần mở rộng SimpleTestCase thay vì TestCase cho các lớp kiểm tra của bạn. TestCase mong đợi một cơ sở dữ liệu.
Ben Roberts

9
Nếu bạn không muốn sử dụng tệp cài đặt mới, bạn có thể chỉ định TestRunner mới trên dòng lệnh với --testrunnertùy chọn.
Bran Handley

26
Câu trả lời chính xác!! Trong django 1.8, từ django.test.simple nhập DjangoTestSuiteRunner đã được đổi thành từ django.test.runner nhập DiscoverRunner Hy vọng giúp được ai đó!
Josh Brown

2
Trong Django 1.8 trở lên, có thể chỉnh sửa một chút cho đoạn mã trên. Có thể thay đổi câu lệnh nhập thành: từ django.test.runner nhập DiscoverRunner Hiện tại NoDbTestRunner phải mở rộng lớp DiscoverRunner.
Aditya Satyavada

77

Nói chung các bài kiểm tra trong một ứng dụng có thể được phân loại thành hai loại

  1. Các bài kiểm tra đơn vị, các bài kiểm tra này kiểm tra các đoạn mã riêng lẻ và không cần phải đi đến cơ sở dữ liệu
  2. Các trường hợp kiểm thử tích hợp thực sự đi đến cơ sở dữ liệu và kiểm tra logic tích hợp đầy đủ.

Django hỗ trợ cả bài kiểm tra đơn vị và tích hợp.

Kiểm tra đơn vị, không yêu cầu thiết lập và phá bỏ cơ sở dữ liệu và chúng tôi nên kế thừa từ SimpleTestCase .

from django.test import SimpleTestCase


class ExampleUnitTest(SimpleTestCase):
    def test_something_works(self):
        self.assertTrue(True)

Đối với các trường hợp kiểm thử tích hợp kế thừa từ TestCase, lần lượt kế thừa từ TransactionTestCase và nó sẽ thiết lập và phá bỏ cơ sở dữ liệu trước khi chạy từng kiểm tra.

from django.test import TestCase


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

Chiến lược này sẽ đảm bảo rằng cơ sở dữ liệu được tạo và hủy chỉ dành cho các trường hợp kiểm tra truy cập cơ sở dữ liệu và do đó các kiểm tra sẽ hiệu quả hơn


37
Điều này có thể làm cho việc chạy thử nghiệm hiệu quả hơn, nhưng lưu ý rằng người chạy thử nghiệm vẫn tạo cơ sở dữ liệu thử nghiệm khi khởi tạo.
monkut

6
Đơn giản hơn nhiều mà câu trả lời được chọn. Cảm ơn bạn rất nhiều!
KFunk

1
@monkut Không ... nếu bạn chỉ có lớp SimpleTestCase, người chạy thử nghiệm không chạy bất cứ thứ gì, hãy xem dự án này .
Claudio Santos

Django vẫn sẽ cố gắng tạo DB thử nghiệm ngay cả khi bạn chỉ sử dụng SimpleTestCase. Xem câu hỏi này .
Marko Prcać

sử dụng SimpleTestCase chính xác hoạt động để kiểm tra các phương thức tiện ích hoặc đoạn mã và không sử dụng hoặc tạo db thử nghiệm. Chính xác những gì tôi cần!
Tyro Hunter

28

Từ django.test.simple

  warnings.warn(
      "The django.test.simple module and DjangoTestSuiteRunner are deprecated; "
      "use django.test.runner.DiscoverRunner instead.",
      RemovedInDjango18Warning)

Vì vậy, ghi đè DiscoverRunnerthay vì 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

Sử dụng như thế:

python manage.py test app --testrunner=app.filename.NoDbTestRunner

8

Tôi đã chọn kế thừa từ django.test.runner.DiscoverRunnervà thực hiện một vài bổ sung cho run_testsphương thức.

Bổ sung đầu tiên của tôi kiểm tra xem việc thiết lập db có cần thiết không và cho phép setup_databaseschức năng bình thường khởi động nếu cần db. Bổ sung thứ hai của tôi cho phép teardown_databaseschạy bình thường nếu setup_databasesphương thức được phép chạy.

Mã của tôi giả định rằng bất kỳ TestCase nào kế thừa từ django.test.TransactionTestCase(và do đó django.test.TestCase) đều yêu cầu cơ sở dữ liệu được thiết lập. Tôi đã đưa ra giả định này bởi vì các tài liệu Django nói:

Nếu bạn cần bất kỳ tính năng nào khác dành riêng cho Django phức tạp và nặng hơn như ... Kiểm tra hoặc sử dụng ORM ... thì bạn nên sử dụng TransactionTestCase hoặc TestCase thay thế.

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

mysite / scripts / 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)

Cuối cùng, tôi đã thêm dòng sau vào tệp settings.txt của dự án.

mysite / settings.py

TEST_RUNNER = 'mysite.scripts.settings.MyDiscoverRunner'

Bây giờ, khi chỉ chạy các thử nghiệm không phụ thuộc db, bộ thử nghiệm của tôi chạy một thứ tự cường độ nhanh hơn! :)


6

Đã cập nhật: cũng xem câu trả lời này để sử dụng công cụ của bên thứ ba pytest.


@Cesar nói đúng. Sau khi vô tình chạy ./manage.py test --settings=no_db_settings, không chỉ định tên ứng dụng, cơ sở dữ liệu phát triển của tôi đã bị xóa sạch.

Để an toàn hơn, hãy sử dụng tương tự NoDbTestRunner, nhưng kết hợp với các cách sau 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'

Bạn cần tạo một cơ sở dữ liệu được gọi _test_mysite_dbbằng cách sử dụng một công cụ cơ sở dữ liệu bên ngoài. Sau đó chạy lệnh sau để tạo các bảng tương ứng:

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

Nếu bạn đang sử dụng Nam, cũng chạy lệnh sau:

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

ĐỒNG Ý!

Bây giờ bạn có thể chạy thử nghiệm đơn vị nhanh chóng (và an toàn) bằng cách:

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

Tôi đã chạy thử nghiệm bằng pytest (với plugin pytest-django) và NoDbTestRunner, nếu bằng cách nào đó bạn tạo một đối tượng một cách tình cờ trong một testcase và bạn không ghi đè tên cơ sở dữ liệu, đối tượng sẽ được tạo trong cơ sở dữ liệu cục bộ mà bạn thiết lập trong cài đặt. Tên 'NoDbTestRunner' phải là 'NoTestDbTestRunner' vì nó sẽ không tạo cơ sở dữ liệu thử nghiệm, nhưng sẽ sử dụng cơ sở dữ liệu của bạn từ cài đặt.
Gabriel Muj

2

Thay thế cho việc sửa đổi cài đặt của bạn để làm cho NoDbTestRunner "an toàn", đây là phiên bản sửa đổi của NoDbTestRunner, đóng kết nối cơ sở dữ liệu hiện tại và xóa thông tin kết nối khỏi cài đặt và đối tượng kết nối. Làm việc cho tôi, kiểm tra nó trong môi trường của bạn trước khi dựa vào nó :)

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

LƯU Ý: Nếu bạn xóa kết nối mặc định khỏi danh sách kết nối, bạn sẽ không thể sử dụng các mô hình Django hoặc các tính năng khác thường sử dụng cơ sở dữ liệu (rõ ràng chúng tôi không liên lạc với cơ sở dữ liệu nhưng Django kiểm tra các tính năng khác nhau mà DB hỗ trợ) . Ngoài ra, có vẻ như các kết nối._connections không hỗ trợ __getitem__nữa. Sử dụng connections._connections.defaultđể truy cập vào đối tượng.
the_drow

2

Một giải pháp khác là để lớp thử nghiệm của bạn đơn giản là kế thừa từ unittest.TestCasethay vì bất kỳ lớp thử nghiệm nào của Django. Các tài liệu Django ( https://docs.djangoproject.com/en/2.0/topics/testing/overview/#wr-tests ) chứa cảnh báo sau về điều này:

Sử dụng unittest.TestCase sẽ tránh được chi phí chạy từng thử nghiệm trong giao dịch và xóa cơ sở dữ liệu, nhưng nếu các thử nghiệm của bạn tương tác với cơ sở dữ liệu thì hành vi của chúng sẽ thay đổi dựa trên thứ tự mà người chạy thử nghiệm thực hiện chúng. Điều này có thể dẫn đến các bài kiểm tra đơn vị vượt qua khi chạy một cách cô lập nhưng không thành công khi chạy trong một bộ.

Tuy nhiên, nếu thử nghiệm của bạn không sử dụng cơ sở dữ liệu, cảnh báo này không liên quan đến bạn và bạn có thể gặt hái những lợi ích của việc không phải chạy từng trường hợp thử nghiệm trong một giao dịch.


Có vẻ như điều này vẫn tạo và phá hủy db, sự khác biệt duy nhất là nó không chạy thử nghiệm trong một giao dịch và không xóa db.
Đường sắt Cam

0

Các giải pháp trên cũng tốt. Nhưng giải pháp sau đây cũng sẽ giảm thời gian tạo db nếu có số lần di chuyển nhiều hơn. Trong quá trình thử nghiệm đơn vị, chạy syncdb thay vì chạy tất cả các di chuyển về phía nam sẽ nhanh hơn nhiều.

SOUTH_TESTS_MIGRATE = Sai # Để vô hiệu hóa di chuyển và sử dụng syncdb thay thế


0

Máy chủ web của tôi chỉ cho phép tạo và xóa cơ sở dữ liệu từ GUI Web của họ, vì vậy tôi đã gặp phải lỗi "Có lỗi khi tạo cơ sở dữ liệu kiểm tra: Quyền bị từ chối" khi thử chạy python manage.py test.

Tôi hy vọng sẽ sử dụng tùy chọn --keepdb cho django-admin.py nhưng dường như nó không còn được hỗ trợ nữa kể từ Django 1.7.

Điều cuối cùng tôi đã làm là sửa đổi mã Django trong ... / django / db / backends / Creation.py, cụ thể là các hàm _create_test_db và _destroy_test_db.

Đối với _create_test_dbtôi nhận xét ra các cursor.execute("CREATE DATABASE ...dòng và thay thế nó bằng passnên trykhối sẽ không được bỏ trống.

_destroy_test_dbtôi vừa mới nhận xét cursor.execute("DROP DATABASE- Tôi không cần thay thế nó bằng bất cứ điều gì vì đã có một lệnh khác trong khối ( time.sleep(1)).

Sau đó, các thử nghiệm của tôi đã chạy tốt - mặc dù tôi đã thiết lập một phiên bản test_ của cơ sở dữ liệu thông thường của mình.

Tất nhiên đây không phải là một giải pháp tuyệt vời, vì nó sẽ bị hỏng nếu Django được nâng cấp, nhưng tôi đã có một bản sao của Django do sử dụng virtualenv nên ít nhất tôi có quyền kiểm soát khi / nếu tôi nâng cấp lên phiên bản mới hơn.


0

Một giải pháp khác không được đề cập: đây là cách dễ dàng để tôi thực hiện vì tôi đã có nhiều tệp cài đặt (cho địa phương / dàn / sản xuất) kế thừa từ cơ sở. Vì vậy, không giống như những người khác, tôi không phải ghi đè lên DATABASES ['mặc định'], vì DATABASES không được đặt trong cơ sở

SimpleTestCase vẫn cố gắng kết nối với cơ sở dữ liệu thử nghiệm của tôi và chạy di chuyển. Khi tôi tạo một tệp cấu hình / settings / test.py không đặt DATABASES thành bất cứ thứ gì, thì các thử nghiệm đơn vị của tôi đã chạy mà không có nó. Nó cho phép tôi sử dụng các mô hình có các trường ràng buộc khóa ngoại và duy nhất. (Đảo ngược tra cứu khóa ngoại, yêu cầu tra cứu db, không thành công.)

(Django 2.0.6)

Đoạn mã 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

0

Khi sử dụng máy chạy thử mũi (django-mũi), bạn có thể làm một cái gì đó như thế này:

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

Trong của settings.pybạn, bạn có thể chỉ định người chạy thử ở đó, tức là

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

HOẶC LÀ

Tôi muốn nó chỉ chạy các thử nghiệm cụ thể, vì vậy tôi chạy nó như vậy:

python manage.py test integration_tests/integration_*  --noinput --testrunner=lib.nodb_test_runner.NoDbTestRunner
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.