มีความเป็นไปได้ไหมที่จะเขียน django unittests โดยไม่ต้องตั้งค่า db? ฉันต้องการทดสอบตรรกะทางธุรกิจที่ไม่ต้องใช้ฐานข้อมูลในการตั้งค่า และในขณะที่ตั้งค่าฐานข้อมูลได้รวดเร็ว แต่ฉันก็ไม่จำเป็นต้องใช้มันในบางสถานการณ์
มีความเป็นไปได้ไหมที่จะเขียน django unittests โดยไม่ต้องตั้งค่า db? ฉันต้องการทดสอบตรรกะทางธุรกิจที่ไม่ต้องใช้ฐานข้อมูลในการตั้งค่า และในขณะที่ตั้งค่าฐานข้อมูลได้รวดเร็ว แต่ฉันก็ไม่จำเป็นต้องใช้มันในบางสถานการณ์
คำตอบ:
คุณสามารถ subclass 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'
UPDATE: เมษายน / 2561
ตั้งแต่ Django 1.8 โมดูลถูกย้ายไปที่.django.test.simple.DjangoTestSuiteRunner
'django.test.runner.DiscoverRunner'
สำหรับข้อมูลเพิ่มเติมโปรดตรวจสอบส่วนเอกสารอย่างเป็นทางการเกี่ยวกับนักวิ่งทดสอบที่กำหนดเอง
--testrunner
ตัวเลือก
โดยทั่วไปการทดสอบในแอปพลิเคชันสามารถแบ่งได้เป็นสองประเภท
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)
กลยุทธ์นี้จะช่วยให้มั่นใจได้ว่าฐานข้อมูลที่สร้างขึ้นและถูกทำลายสำหรับกรณีทดสอบที่เข้าถึงฐานข้อมูลเท่านั้นดังนั้นการทดสอบจะมีประสิทธิภาพมากขึ้น
จาก 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
ฉันเลือกที่จะสืบทอดจากdjango.test.runner.DiscoverRunner
และเพิ่มสองสามrun_tests
วิธีในวิธีนี้
การเพิ่มครั้งแรกของฉันตรวจสอบเพื่อดูว่าจำเป็นต้องตั้งค่าฐานข้อมูลหรือไม่และอนุญาตให้setup_databases
ฟังก์ชันปกติเริ่มทำงานได้หากจำเป็นต้องใช้ฐานข้อมูล การเพิ่มครั้งที่สองของฉันอนุญาตให้ทำงานปกติteardown_databases
หากsetup_databases
วิธีการได้รับอนุญาตให้ทำงาน
รหัสของฉันถือว่า TestCase ใด ๆ ที่สืบทอดมาจากdjango.test.TransactionTestCase
(และดังนั้นdjango.test.TestCase
) ต้องมีการตั้งค่าฐานข้อมูล ฉันตั้งสมมติฐานนี้เพราะเอกสาร Django บอกว่า:
หากคุณต้องการคุณสมบัติเฉพาะของ Django ที่ซับซ้อนและมีน้ำหนักมากเช่น ... การทดสอบหรือใช้ ORM ... คุณควรใช้ TransactionTestCase หรือ TestCase แทน
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)
สุดท้ายฉันได้เพิ่มบรรทัดต่อไปนี้ในไฟล์ settings.py ของโปรเจ็กต์
TEST_RUNNER = 'mysite.scripts.settings.MyDiscoverRunner'
ตอนนี้เมื่อเรียกใช้เฉพาะการทดสอบที่ไม่ขึ้นกับฐานข้อมูลชุดทดสอบของฉันจะทำงานตามลำดับขนาดได้เร็วขึ้น! :)
อัปเดต:ยังเห็นคำตอบนี้pytest
สำหรับการใช้เครื่องมือของบุคคลที่สาม
@ ซีซาร์พูดถูก หลังจากทำงาน./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
ตกลง!
ตอนนี้คุณสามารถเรียกใช้การทดสอบหน่วยได้อย่างรวดเร็ว (และปลอดภัย) โดย:
./manage.py test myapp --settings=mysite.no_db_settings
เพื่อเป็นทางเลือกในการแก้ไขการตั้งค่าของคุณเพื่อให้ 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
__getitem__
อีกต่อไป ใช้connections._connections.default
เพื่อเข้าถึงวัตถุ
อีกวิธีหนึ่งคือให้คลาสทดสอบของคุณสืบทอดมาunittest.TestCase
แทนที่จะเป็นคลาสทดสอบของ Django เอกสาร Django ( https://docs.djangoproject.com/en/2.0/topics/testing/overview/#writing-tests ) มีคำเตือนต่อไปนี้เกี่ยวกับสิ่งนี้:
การใช้ unittest.TestCase หลีกเลี่ยงค่าใช้จ่ายในการดำเนินการทดสอบแต่ละครั้งในธุรกรรมและล้างฐานข้อมูล แต่ถ้าการทดสอบของคุณโต้ตอบกับฐานข้อมูลพฤติกรรมของพวกเขาจะแตกต่างกันไปตามลำดับที่ผู้ทดสอบดำเนินการ สิ่งนี้สามารถนำไปสู่การทดสอบหน่วยที่ผ่านเมื่อรันแบบแยกส่วน แต่ล้มเหลวเมื่อรันในชุด
อย่างไรก็ตามหากการทดสอบของคุณไม่ใช้ฐานข้อมูลคำเตือนนี้ไม่จำเป็นต้องเกี่ยวข้องกับคุณและคุณสามารถเก็บเกี่ยวผลประโยชน์จากการไม่ต้องรันแต่ละกรณีทดสอบในธุรกรรม
วิธีแก้ปัญหาข้างต้นก็ใช้ได้เช่นกัน แต่วิธีแก้ปัญหาต่อไปนี้จะช่วยลดเวลาในการสร้างฐานข้อมูลหากมีจำนวนการย้ายข้อมูลมากขึ้น ในระหว่างการทดสอบหน่วยการเรียกใช้ syncdb แทนที่จะเรียกใช้การย้ายข้อมูลทางทิศใต้ทั้งหมดจะเร็วกว่ามาก
SOUTH_TESTS_MIGRATE = False # เพื่อปิดใช้งานการย้ายข้อมูลและใช้ syncdb แทน
เว็บโฮสต์ของฉันเพียง แต่ช่วยให้การสร้างและวางฐานข้อมูลจากเว็บ GUI ของพวกเขาดังนั้นผมได้รับเป็น "มีข้อผิดพลาดในการสร้างฐานข้อมูลการทดสอบการอนุญาตปฏิเสธข้อผิดพลาด" python manage.py test
เมื่อพยายามที่จะวิ่ง
ฉันหวังว่าจะใช้ตัวเลือก --keepdb เพื่อ django-admin.py แต่ดูเหมือนว่าจะไม่ได้รับการสนับสนุนอีกต่อไปตั้งแต่ Django 1.7
สิ่งที่ฉันทำคือแก้ไขโค้ด Django ใน ... / django / db / backends / creation.py โดยเฉพาะฟังก์ชัน _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 ได้รับการอัพเกรด แต่ฉันมีสำเนาของ Django ในเครื่องเนื่องจากใช้ Virtualenv ดังนั้นอย่างน้อยฉันก็สามารถควบคุมได้ว่าจะอัปเกรดเป็นเวอร์ชันที่ใหม่กว่าเมื่อใด / หาก
วิธีแก้ปัญหาอื่นที่ไม่ได้กล่าวถึง: นี่เป็นเรื่องง่ายสำหรับฉันที่จะนำไปใช้เพราะฉันมีไฟล์การตั้งค่าหลายไฟล์ (สำหรับโลคัล / การจัดเตรียม / การผลิต) ที่สืบทอดมาจาก 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
เมื่อใช้นักวิ่งทดสอบจมูก (django-nose) คุณสามารถทำสิ่งนี้ได้:
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
คุณคุณสามารถระบุนักวิ่งทดสอบได้ที่นั่นเช่น
TEST_RUNNER = 'lib.nodb_test_runner.NoDbTestRunner' . # Was 'django_nose.NoseTestSuiteRunner'
หรือ
ฉันต้องการให้ทำการทดสอบเฉพาะเท่านั้นดังนั้นฉันจึงเรียกใช้ดังนี้:
python manage.py test integration_tests/integration_* --noinput --testrunner=lib.nodb_test_runner.NoDbTestRunner