Apakah ada kemungkinan untuk menulis django unittests tanpa membuat db? Saya ingin menguji logika bisnis yang tidak memerlukan pengaturan db. Dan sementara itu cepat untuk mengatur db, saya benar-benar tidak membutuhkannya dalam beberapa situasi.
Apakah ada kemungkinan untuk menulis django unittests tanpa membuat db? Saya ingin menguji logika bisnis yang tidak memerlukan pengaturan db. Dan sementara itu cepat untuk mengatur db, saya benar-benar tidak membutuhkannya dalam beberapa situasi.
Jawaban:
Anda dapat mensubklasifikasikan DjangoTestSuiteRunner dan mengganti metode setup_databases dan teardown_databases untuk dilewati.
Buat file pengaturan baru dan atur TEST_RUNNER ke kelas baru yang baru saja Anda buat. Kemudian ketika Anda menjalankan tes Anda, tentukan file pengaturan baru Anda dengan flag --settings.
Inilah yang saya lakukan:
Buat pelari setelan ujian khusus yang mirip dengan ini:
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
Buat pengaturan khusus:
from mysite.settings import *
# Test runner with no database creation
TEST_RUNNER = 'mysite.scripts.testrunner.NoDbTestRunner'
Saat Anda menjalankan tes, jalankan seperti berikut ini dengan --settings flag diset ke file pengaturan baru Anda:
python manage.py test myapp --settings='no_db_settings'
PEMBARUAN: April / 2018
Sejak Django 1.8, modul dipindahkan ke .django.test.simple.DjangoTestSuiteRunner
'django.test.runner.DiscoverRunner'
Untuk info lebih lanjut, periksa bagian dokumen resmi tentang pelari ujian khusus.
--testrunner
opsi.
Secara umum tes dalam suatu aplikasi dapat diklasifikasikan ke dalam dua kategori
Django mendukung pengujian unit dan integrasi.
Unit test, tidak perlu melakukan setup dan meruntuhkan basis data dan ini harus kita warisi dari SimpleTestCase .
from django.test import SimpleTestCase
class ExampleUnitTest(SimpleTestCase):
def test_something_works(self):
self.assertTrue(True)
Untuk kasus uji integrasi, mewarisi dari TestCase pada gilirannya mewarisi dari TransactionTestCase dan itu akan mengatur dan meruntuhkan database sebelum menjalankan setiap tes.
from django.test import TestCase
class ExampleIntegrationTest(TestCase):
def test_something_works(self):
#do something with database
self.assertTrue(True)
Strategi ini akan memastikan bahwa basis data dibuat dan dimusnahkan hanya untuk kasus uji yang mengakses database dan karenanya pengujian akan lebih efisien
Dari django.test.simple
warnings.warn(
"The django.test.simple module and DjangoTestSuiteRunner are deprecated; "
"use django.test.runner.DiscoverRunner instead.",
RemovedInDjango18Warning)
Jadi DiscoverRunner
ganti alih-alih 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
Gunakan seperti itu:
python manage.py test app --testrunner=app.filename.NoDbTestRunner
Saya memilih untuk mewarisi django.test.runner.DiscoverRunner
dan membuat beberapa tambahan kerun_tests
metode ini.
Penambahan pertama saya memeriksa apakah pengaturan db diperlukan dan memungkinkan setup_databases
fungsi normal untuk memulai jika db diperlukan. Tambahan kedua saya memungkinkan normal teardown_databases
untuk berjalan jika setup_databases
metode itu diizinkan untuk dijalankan.
Kode saya mengasumsikan bahwa setiap TestCase yang mewarisi dari django.test.TransactionTestCase
(dan dengan demikian django.test.TestCase
) memerlukan database yang harus disiapkan. Saya membuat asumsi ini karena dokumen Django mengatakan:
Jika Anda memerlukan fitur spesifik Django yang lebih kompleks dan berat seperti ... Menguji atau menggunakan ORM ... maka Anda harus menggunakan TransactionTestCase atau TestCase sebagai gantinya.
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)
Akhirnya, saya menambahkan baris berikut ke file settings.py proyek saya.
TEST_RUNNER = 'mysite.scripts.settings.MyDiscoverRunner'
Sekarang, ketika menjalankan hanya tes yang tidak bergantung db, suite pengujian saya menjalankan urutan besarnya lebih cepat! :)
Diperbarui: lihat juga jawaban ini untuk menggunakan alat pihak ketiga pytest
.
@ Caesar benar. Setelah berjalan secara tidak sengaja ./manage.py test --settings=no_db_settings
, tanpa menentukan nama aplikasi, basis data pengembangan saya terhapus.
Untuk cara yang lebih aman, gunakan hal yang sama NoDbTestRunner
, tetapi dalam hubungannya dengan yang berikut 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'
Anda perlu membuat database yang disebut _test_mysite_db
menggunakan alat basis data eksternal. Kemudian jalankan perintah berikut untuk membuat tabel terkait:
./manage.py syncdb --settings=mysite.no_db_settings
Jika Anda menggunakan Selatan, jalankan juga perintah berikut:
./manage.py migrate --settings=mysite.no_db_settings
BAIK!
Anda sekarang dapat menjalankan tes unit dengan sangat cepat (dan aman) dengan:
./manage.py test myapp --settings=mysite.no_db_settings
Sebagai alternatif untuk memodifikasi pengaturan Anda untuk membuat NoDbTestRunner "aman", inilah versi modifikasi dari NoDbTestRunner yang menutup koneksi database saat ini dan menghapus informasi koneksi dari pengaturan dan objek koneksi. Berfungsi untuk saya, ujilah di lingkungan Anda sebelum mengandalkannya :)
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__
lagi. Gunakan connections._connections.default
untuk mengakses objek.
Solusi lain adalah dengan membuat kelas tes Anda hanya mewarisi dari unittest.TestCase
bukan kelas tes Django. Dokumen Django ( https://docs.djangoproject.com/en/2.0/topics/testing/overview/#writing-tests ) berisi peringatan berikut ini:
Menggunakan unittest.TestCase menghindari biaya menjalankan setiap tes dalam transaksi dan membilas basis data, tetapi jika tes Anda berinteraksi dengan basis data, perilaku mereka akan bervariasi berdasarkan pada urutan yang dijalankan oleh pelari uji. Ini dapat menyebabkan unit test yang lulus saat dijalankan dalam isolasi tetapi gagal saat dijalankan dalam suite.
Namun, jika tes Anda tidak menggunakan database, peringatan ini tidak perlu Anda khawatirkan dan Anda dapat menuai manfaat karena tidak harus menjalankan setiap kasus uji dalam transaksi.
Solusi di atas juga baik-baik saja. Tetapi solusi berikut ini juga akan mengurangi waktu pembuatan db jika ada lebih banyak jumlah migrasi. Selama pengujian unit, menjalankan syncdb alih-alih menjalankan semua migrasi selatan akan jauh lebih cepat.
SOUTH_TESTS_MIGRATE = Salah # Untuk menonaktifkan migrasi dan gunakan syncdb
Host web saya hanya memungkinkan membuat dan menjatuhkan basis data dari GUI Web mereka, jadi saya mendapatkan kesalahan "Mendapat kesalahan saat membuat basis data pengujian: Izin ditolak" ketika mencoba menjalankan python manage.py test
.
Saya berharap untuk menggunakan opsi --keepdb untuk django-admin.py tetapi sepertinya tidak didukung lagi pada Django 1.7.
Yang akhirnya saya lakukan adalah memodifikasi kode Django di ... / django / db / backends / creation.py, khususnya fungsi _create_test_db dan _destroy_test_db.
Karena _create_test_db
saya berkomentar keluar cursor.execute("CREATE DATABASE ...
garis dan menggantinya dengan pass
begitutry
blok tidak akan kosong.
Karena _destroy_test_db
saya baru saja berkomentar cursor.execute("DROP DATABASE
- saya tidak perlu menggantinya dengan apa pun karena sudah ada perintah lain di blok (time.sleep(1)
).
Setelah itu pengujian saya berjalan dengan baik - walaupun saya telah membuat versi test_ dari database reguler saya secara terpisah.
Ini bukan solusi yang bagus tentu saja, karena akan rusak jika Django ditingkatkan, tapi saya punya salinan lokal Django karena menggunakan virtualenv jadi setidaknya saya memiliki kendali atas kapan / jika saya meng-upgrade ke versi yang lebih baru.
Solusi lain yang tidak disebutkan: ini mudah bagi saya untuk mengimplementasikan karena saya sudah memiliki banyak file pengaturan (untuk lokal / pementasan / produksi) yang mewarisi dari base.py. Jadi tidak seperti orang lain, saya tidak perlu menimpa DATABASES ['default'], karena DATABASES tidak diset di base.py
SimpleTestCase masih mencoba untuk terhubung ke database pengujian saya dan menjalankan migrasi. Ketika saya membuat file config / settings / test.py yang tidak mengatur DATABASES untuk apa pun, maka pengujian unit saya berjalan tanpa itu. Itu memungkinkan saya untuk menggunakan model yang memiliki kunci asing dan bidang kendala unik. (Membalikkan pencarian kunci asing, yang membutuhkan pencarian db, gagal.)
(Django 2.0.6)
Cuplikan kode 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
Saat menggunakan pelari tes hidung (django-nose), Anda dapat melakukan sesuatu seperti ini:
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
Di Anda, settings.py
Anda dapat menentukan pelari tes di sana, yaitu
TEST_RUNNER = 'lib.nodb_test_runner.NoDbTestRunner' . # Was 'django_nose.NoseTestSuiteRunner'
ATAU
Saya menginginkannya untuk menjalankan tes tertentu saja, jadi saya menjalankannya seperti ini:
python manage.py test integration_tests/integration_* --noinput --testrunner=lib.nodb_test_runner.NoDbTestRunner