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.
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.
Câu trả lời:
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.
--testrunner
tùy chọn.
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
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
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 đè DiscoverRunner
thay 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
Tôi đã chọn kế thừa từ django.test.runner.DiscoverRunner
và thực hiện một vài bổ sung cho run_tests
phươ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_databases
chứ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_databases
chạy bình thường nếu setup_databases
phươ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
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.
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! :)
Đã 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_db
bằ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
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
__getitem__
nữa. Sử dụng connections._connections.default
để truy cập vào đối tượng.
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.TestCase
thay 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á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ế
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_db
tôi nhận xét ra các cursor.execute("CREATE DATABASE ...
dòng và thay thế nó bằng pass
nên try
khối sẽ không được bỏ trống.
Vì _destroy_test_db
tô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.
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
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.py
bạ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