Bir db kurmadan django birim testleri yazma imkanı var mı? DB'nin kurulmasını gerektirmeyen iş mantığını test etmek istiyorum. Ve bir db kurmak hızlı olsa da, bazı durumlarda gerçekten ihtiyacım yok.
Bir db kurmadan django birim testleri yazma imkanı var mı? DB'nin kurulmasını gerektirmeyen iş mantığını test etmek istiyorum. Ve bir db kurmak hızlı olsa da, bazı durumlarda gerçekten ihtiyacım yok.
Yanıtlar:
DjangoTestSuiteRunner'ı alt sınıflandırabilir ve geçmek için setup_databases ve teardown_databases yöntemlerini geçersiz kılabilirsiniz.
Yeni bir ayarlar dosyası oluşturun ve TEST_RUNNER'ı az önce oluşturduğunuz yeni sınıfa ayarlayın. Sonra testinizi çalıştırırken, yeni ayarlar dosyanızı --settings bayrağıyla belirtin.
İşte yaptığım şey:
Buna benzer özel bir test kıyafeti koşucusu oluşturun:
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
Özel bir ayar oluşturun:
from mysite.settings import *
# Test runner with no database creation
TEST_RUNNER = 'mysite.scripts.testrunner.NoDbTestRunner'
Testlerinizi çalıştırırken, yeni ayarlar dosyanıza --settings bayrağı ayarlanmış şekilde aşağıdaki gibi çalıştırın:
python manage.py test myapp --settings='no_db_settings'
GÜNCELLEME: Nisan / 2018
Django 1.8'den beri, modül şu adrese taşındı :django.test.simple.DjangoTestSuiteRunner
'django.test.runner.DiscoverRunner'
.
Daha fazla bilgi için özel test koşucuları hakkındaki resmi belge bölümüne bakın.
--testrunner
seçenekle belirtebilirsiniz .
Genel olarak bir uygulamadaki testler iki kategoriye ayrılabilir
Django hem birim hem de entegrasyon testlerini destekler.
Birim testleri, veritabanının kurulmasına ve yırtılmasına gerek yoktur ve bunları SimpleTestCase'den miras almalıyız .
from django.test import SimpleTestCase
class ExampleUnitTest(SimpleTestCase):
def test_something_works(self):
self.assertTrue(True)
Entegrasyon testi senaryoları için TestCase'den devralır, sırayla TransactionTestCase'den devralır ve her testi çalıştırmadan önce veritabanını kurar ve ortadan kaldırır.
from django.test import TestCase
class ExampleIntegrationTest(TestCase):
def test_something_works(self):
#do something with database
self.assertTrue(True)
Bu strateji, veritabanının yalnızca veritabanına erişen test senaryoları için oluşturulmasını ve yok edilmesini ve dolayısıyla testlerin daha verimli olmasını sağlayacaktır.
itibaren django.test.simple
warnings.warn(
"The django.test.simple module and DjangoTestSuiteRunner are deprecated; "
"use django.test.runner.DiscoverRunner instead.",
RemovedInDjango18Warning)
Yani DiscoverRunner
yerine geçersiz kıl 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
Bunun gibi kullanın:
python manage.py test app --testrunner=app.filename.NoDbTestRunner
Yönteme miras almayı django.test.runner.DiscoverRunner
ve birkaç ekleme yapmayı seçtim run_tests
.
İlk eklemem bir db kurmanın gerekli olup olmadığını kontrol eder ve bir db gerekliyse normal setup_databases
işlevselliğin devreye girmesine izin verir . İkinci eklemem teardown_databases
, setup_databases
yöntemin çalışmasına izin verilirse normalin çalışmasına izin verir.
Kodum, herhangi bir TestCase'in devraldığı django.test.TransactionTestCase
(ve dolayısıyla django.test.TestCase
) bir veritabanının kurulmasını gerektirdiğini varsayar . Bu varsayımı yaptım çünkü Django belgeleri şunları söylüyor:
ORM'yi test etmek veya kullanmak gibi diğer daha karmaşık ve ağır Django'ya özgü özelliklerden herhangi birine ihtiyacınız varsa, bunun yerine TransactionTestCase veya TestCase'i kullanmalısınız.
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)
Son olarak, projemin settings.py dosyasına aşağıdaki satırı ekledim.
TEST_RUNNER = 'mysite.scripts.settings.MyDiscoverRunner'
Artık, yalnızca db'ye bağlı olmayan testleri çalıştırırken, test paketim bir sıra daha hızlı çalıştırıyor! :)
Güncellendi: üçüncü taraf bir araç kullanmak için bu yanıta da bakın pytest
.
@Cesar haklı. Yanlışlıkla koştuktan sonra./manage.py test --settings=no_db_settings
, bir uygulama adı belirtmeden geliştirme veritabanım silindi.
Daha güvenli bir şekilde, aynısını kullanın NoDbTestRunner
, ancak aşağıdakilerle birlikte 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
Harici bir veritabanı aracı kullanarak adlandırılan bir veritabanı oluşturmanız gerekir . Ardından ilgili tabloları oluşturmak için aşağıdaki komutu çalıştırın:
./manage.py syncdb --settings=mysite.no_db_settings
Güney kullanıyorsanız, aşağıdaki komutu da çalıştırın:
./manage.py migrate --settings=mysite.no_db_settings
TAMAM!
Artık aşağıdakileri yaparak birim testlerini son derece hızlı (ve güvenli) çalıştırabilirsiniz:
./manage.py test myapp --settings=mysite.no_db_settings
NoDbTestRunner'ı "güvenli" yapmak için ayarlarınızı değiştirmeye bir alternatif olarak, işte mevcut veritabanı bağlantısını kapatan ve bağlantı bilgilerini ayarlardan ve bağlantı nesnesinden kaldıran değiştirilmiş bir NoDbTestRunner sürümü. Benim için çalışıyor, güvenmeden önce ortamınızda test edin :)
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__
artık desteklemediği görülüyor . connections._connections.default
Nesneye erişmek için kullanın .
Başka bir çözüm, test sınıfınızın unittest.TestCase
, Django'nun herhangi bir test sınıfının yerine basitçe miras almasıdır. Django belgeleri ( https://docs.djangoproject.com/en/2.0/topics/testing/overview/#writing-tests ) bununla ilgili aşağıdaki uyarıyı içerir:
Unittest.TestCase kullanmak, her testi bir işlemde çalıştırma ve veritabanını temizleme maliyetini ortadan kaldırır, ancak testleriniz veritabanıyla etkileşime girerse, davranışları test çalıştırıcısının bunları yürütme sırasına göre değişecektir. Bu, izolasyonda çalıştırıldığında başarılı olan ancak bir pakette çalıştırıldığında başarısız olan birim testlerine yol açabilir.
Bununla birlikte, testiniz veritabanını kullanmıyorsa, bu uyarının sizi ilgilendirmesine gerek yoktur ve bir işlemde her bir test senaryosunu çalıştırmak zorunda kalmamanın avantajlarından yararlanabilirsiniz.
Yukarıdaki çözümler de iyidir. Ancak aşağıdaki çözüm, daha fazla sayıda geçiş olması durumunda db oluşturma süresini de azaltacaktır. Birim testi sırasında, tüm güney geçişlerini çalıştırmak yerine syncdb çalıştırmak çok daha hızlı olacaktır.
SOUTH_TESTS_MIGRATE = False # Taşıma işlemlerini devre dışı bırakmak ve bunun yerine syncdb kullanmak için
Web barındırıcım yalnızca kendi Web GUI'lerinden veritabanları oluşturmaya ve bırakmaya izin veriyor, bu nedenle çalıştırmaya çalışırken "Test veritabanı oluşturulurken bir hata oluştu: İzin reddedildi" hatası alıyordum python manage.py test
.
Django-admin.py için --keepdb seçeneğini kullanmayı umuyordum ama artık Django 1.7'den itibaren desteklenmiyor gibi görünüyor.
Sonunda yaptığım şey, ... / django / db / backends / create.py'deki Django kodunu, özellikle _create_test_db ve _destroy_test_db işlevlerini değiştirmekti.
Çünkü çizgiyi _create_test_db
yorumladım cursor.execute("CREATE DATABASE ...
ve onu pass
şu şekilde değiştirdim:try
bloğun boş kalmaması için .
Çünkü _destroy_test_db
az önce yorum cursor.execute("DROP DATABASE
yaptım - onu herhangi bir şeyle değiştirmeme gerek yoktu çünkü blokta zaten başka bir komut vardı (time.sleep(1)
) .
Bundan sonra, normal veritabanımın bir test sürümünü ayrı ayrı kurmuş olsam da, testlerim iyi gitti.
Elbette bu harika bir çözüm değil, çünkü Django yükseltilirse bozulacak, ancak virtualenv kullanmaktan dolayı Django'nun yerel bir kopyasına sahiptim, bu yüzden en azından daha yeni bir sürüme ne zaman / ne zaman yükselteceğimi kontrol edebiliyorum.
Bahsedilmeyen başka bir çözüm: bu benim için kolaydı çünkü zaten base.py'den devralan birden fazla ayar dosyam (yerel / hazırlama / üretim için) var. Bu yüzden diğer insanlardan farklı olarak, base.py'de VERİTABANLARI ayarlanmadığı için DATABASES ['varsayılan'] üzerine yazmak zorunda değildim
SimpleTestCase hala test veritabanıma bağlanmayı ve geçişleri çalıştırmayı denedi. DATABASES'ı hiçbir şeye ayarlamayan bir config / settings / test.py dosyası oluşturduğumda, birim testlerim onsuz çalıştı. Yabancı anahtar ve benzersiz kısıtlama alanlarına sahip modelleri kullanmama izin verdi. (Bir db araması gerektiren ters yabancı anahtar araması başarısız olur.)
(Django 2.0.6)
PS kod parçacıkları
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
Burun testi koşucusunu (django-burun) kullanırken, şöyle bir şey yapabilirsiniz:
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
Gözlerinde farklı settings.py
orada Test koşucu belirtebilirsiniz, yani
TEST_RUNNER = 'lib.nodb_test_runner.NoDbTestRunner' . # Was 'django_nose.NoseTestSuiteRunner'
VEYA
Yalnızca belirli testleri çalıştırmak için istedim, bu yüzden şu şekilde çalıştırdım:
python manage.py test integration_tests/integration_* --noinput --testrunner=lib.nodb_test_runner.NoDbTestRunner