db olmadan django birim testleri


127

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.


Bunun gerçekten önemli olup olmadığını merak ediyorum. Db bellekte tutulur + eğer herhangi bir modeliniz yoksa db ile hiçbir şey yapılmaz. Yani ihtiyacınız yoksa modelleri kurmayın.
Torsten Engelbrecht

3
Modellerim var, ancak bu testler için uygun değiller. Ve db bellekte tutulmaz, ancak mysql'de oluşturulur, ancak özellikle bu amaç için. Bunu istediğimden değil .. Belki django'yu test için bellek içi bir db kullanacak şekilde yapılandırabilirim. Bunun nasıl yapılacağını biliyor musun?
paweloque

Üzgünüm. Bellek içi veritabanları, bir SQLite veritabanı kullandığınızda oluşan durumdur. Bunun dışında test db oluşturmaktan kaçınmanın bir yolunu görmüyorum. Dokümanlarda bununla ilgili hiçbir şey yok + Bundan kaçınmaya hiç ihtiyaç duymadım.
Torsten Engelbrecht

3
Kabul edilen cevap bende işe yaramadı. Bunun yerine, bu mükemmel çalıştı: caktusgroup.com/blog/2013/10/02/skipping-test-db-creation
Hugo Pineda

Yanıtlar:


122

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.


2
Bu hata, veritabanı işlemlerine ihtiyaç duyan testleriniz olduğunda ortaya çıkar. Açıkçası, bir DB'niz yoksa, bu testleri çalıştıramayacaksınız. Testlerinizi ayrı ayrı yapmalısınız. Testinizi python manage.py test --settings = new_settings.py kullanarak çalıştırırsanız, veritabanı gerektirebilecek diğer uygulamalardan bir dizi başka test çalıştıracaktır.
mohi666

5
Test sınıflarınız için TestCase yerine SimpleTestCase'i genişletmeniz gerekeceğini unutmayın. TestCase bir veritabanı bekliyor.
Ben Roberts

9
Yeni bir ayarlar dosyası kullanmak istemiyorsanız, yeni TestRunner'ı komut satırında --testrunnerseçenekle belirtebilirsiniz .
Bran Handley

26
Mükemmel cevap!! Django 1.8'de, django.test.simple import konumundan DjangoTestSuiteRunner, django.test.runner ithal olarak değiştirilmiştir DiscoverRunner birisine yardım eden umut!
Josh Brown

2
Django 1.8 ve üzeri sürümlerde, yukarıdaki kodda küçük bir düzeltme yapılabilir. Import ifadesi şu şekilde değiştirilebilir: from django.test.runner import DiscoverRunner NoDbTestRunner artık DiscoverRunner sınıfını genişletmelidir.
Aditya Satyavada

78

Genel olarak bir uygulamadaki testler iki kategoriye ayrılabilir

  1. Birim testleri, bunlar ayrı ayrı kod parçacıklarını güneşte test eder ve veritabanına gitmeyi gerektirmez.
  2. Veritabanına giden ve tam entegre mantığı test eden entegrasyon testi durumları.

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.


37
Bu, testleri çalıştırmayı daha verimli hale getirebilir, ancak test çalıştırıcısının yine de başlatma sırasında test veritabanları oluşturduğunu unutmayın.
monkut

6
O kadar basit ki seçilen cevap. Çok teşekkür ederim!
KFunk

1
@monkut Hayır ... Eğer sadece SimpleTestCase sınıfınız varsa, test çalıştırıcısı hiçbir şey çalıştırmaz, bu projeye bakın .
Claudio Santos

Django, yalnızca SimpleTestCase kullansanız bile bir test DB oluşturmaya çalışacaktır. Bu soruya bakın .
Marko Prcać

SimpleTestCase kullanmak, yardımcı program yöntemlerini veya parçacıklarını test etmek için tam olarak çalışır ve test veritabanı kullanmaz veya oluşturmaz. Tam olarak ihtiyacım olan şey!
Tyro Hunter

28

itibaren django.test.simple

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

Yani DiscoverRunneryerine 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

8

Yönteme miras almayı django.test.runner.DiscoverRunnerve 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_databasesişlevselliğin devreye girmesine izin verir . İkinci eklemem teardown_databases, setup_databasesyö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

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

Son olarak, projemin settings.py dosyasına aşağıdaki satırı ekledim.

mysite / settings.py

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! :)


6

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_dbHarici 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

Pytest (pytest-django eklentisi ile) ve NoDbTestRunner kullanarak testler yaptım, bir şekilde bir test olayında kazara bir nesne oluşturursanız ve veritabanı adını geçersiz kılmazsanız, nesne yerel veritabanlarınızda oluşturulacaktır. ayarlar. 'NoDbTestRunner' adı 'NoTestDbTestRunner' olmalıdır çünkü test veritabanını oluşturmaz, ancak veritabanınızı ayarlardan kullanır.
Gabriel Muj

2

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

NOT: Varsayılan bağlantıyı bağlantılar listesinden silerseniz, Django modellerini veya normalde veritabanını kullanan diğer özellikleri kullanamazsınız (tabii ki veritabanı ile iletişim kurmuyoruz, ancak Django DB'nin desteklediği farklı özellikleri kontrol ediyor) . Ayrıca links._connections'ın __getitem__artık desteklemediği görülüyor . connections._connections.defaultNesneye erişmek için kullanın .
the_drow

2

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.


Görünüşe göre bu hala db'yi yaratıyor ve yok ediyor, tek fark, testi bir işlemde çalıştırmaması ve db'yi temizlememesidir.
Cam Ray

0

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


0

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_dbyorumladım cursor.execute("CREATE DATABASE ...ve onu passşu şekilde değiştirdim:try bloğun boş kalmaması için .

Çünkü _destroy_test_dbaz önce yorum cursor.execute("DROP DATABASEyaptı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.


0

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

0

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.pyorada 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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.