unit tes django tanpa db

126

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.

paweloque
sumber
Saya bertanya-tanya apakah itu benar-benar penting. Db disimpan dalam memori + jika Anda tidak memiliki model apa pun tidak dilakukan dengan db. Jadi, jika Anda tidak membutuhkannya, jangan membuat model.
Torsten Engelbrecht
3
Saya memang punya model, tetapi untuk tes itu tidak relevan. Dan db tidak disimpan dalam memori, tetapi dibangun di mysql, bagaimanapun, khusus untuk tujuan ini. Bukannya saya menginginkan ini .. Mungkin saya bisa mengkonfigurasi Django untuk menggunakan db dalam memori untuk pengujian. Apakah Anda tahu bagaimana melakukan ini?
paweloque
Oh, maafkan saya. Basis data dalam memori hanyalah kasus ketika Anda menggunakan basis data SQLite. Kecuali ini saya tidak melihat cara untuk menghindari membuat tes db. Tidak ada apa-apa tentang ini dalam dokumen + Saya tidak pernah merasa perlu untuk menghindarinya.
Torsten Engelbrecht
3
Jawaban yang diterima tidak berhasil bagi saya. Sebagai gantinya, ini bekerja dengan sempurna: caktusgroup.com/blog/2013/10/02/skipping-test-db-creation
Hugo Pineda

Jawaban:

122

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.

mohi666
sumber
2
Kesalahan ini muncul ketika Anda memiliki tes yang membutuhkan transaksi basis data. Tentunya jika Anda tidak memiliki DB, Anda tidak akan dapat menjalankan tes itu. Anda harus menjalankan tes Anda secara terpisah. Jika Anda hanya menjalankan tes dengan menggunakan uji python manage.py --settings = new_settings.py, itu akan menjalankan sejumlah tes lain dari aplikasi lain yang mungkin memerlukan basis data.
mohi666
5
Perhatikan bahwa Anda harus memperluas SimpleTestCase daripada TestCase untuk kelas tes Anda. TestCase mengharapkan basis data.
Ben Roberts
9
Jika Anda tidak ingin menggunakan file pengaturan baru Anda dapat menentukan TestRunner baru pada baris perintah dengan --testrunneropsi.
Bran Handley
26
Jawaban bagus !! Di django 1.8, dari django.test.simple import DjangoTestSuiteRunner telah diubah menjadi dari django.test.runner import DiscoverRunner Hope yang membantu seseorang!
Josh Brown
2
Dalam Django 1.8 dan di atasnya, sedikit koreksi terhadap kode di atas dapat dilakukan. Pernyataan impor dapat diubah menjadi: dari django.test.runner import DiscoverRunner NoDbTestRunner sekarang harus memperluas kelas DiscoverRunner.
Aditya Satyavada
77

Secara umum tes dalam suatu aplikasi dapat diklasifikasikan ke dalam dua kategori

  1. Tes unit, ini menguji potongan kode individu dalam isolasi dan tidak perlu pergi ke database
  2. Kasus uji integrasi yang benar-benar pergi ke database dan menguji logika yang sepenuhnya terintegrasi.

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

Ali
sumber
37
Ini mungkin membuat tes berjalan lebih efisien, tetapi perhatikan bahwa pelari tes masih membuat database uji pada inisialisasi.
monkut
6
Jauh lebih sederhana dari jawaban yang dipilih. Terima kasih banyak!
KFunk
1
@monkut Tidak ... jika Anda hanya memiliki kelas SimpleTestCase, pelari ujian tidak menjalankan apa pun, lihat proyek ini .
Claudio Santos
Django masih akan mencoba untuk membuat tes DB bahkan jika Anda hanya menggunakan SimpleTestCase. Lihat pertanyaan ini .
Marko Prcać
menggunakan SimpleTestCase persis berfungsi untuk menguji metode utilitas atau cuplikan dan tidak menggunakan atau membuat test db. Apa yang saya butuhkan!
Tyro Hunter
28

Dari django.test.simple

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

Jadi DiscoverRunnerganti 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
themadmax
sumber
8

Saya memilih untuk mewarisi django.test.runner.DiscoverRunnerdan membuat beberapa tambahan kerun_tests metode ini.

Penambahan pertama saya memeriksa apakah pengaturan db diperlukan dan memungkinkan setup_databasesfungsi normal untuk memulai jika db diperlukan. Tambahan kedua saya memungkinkan normal teardown_databasesuntuk berjalan jika setup_databasesmetode 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

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

Akhirnya, saya menambahkan baris berikut ke file settings.py proyek saya.

mysite / settings.py

TEST_RUNNER = 'mysite.scripts.settings.MyDiscoverRunner'

Sekarang, ketika menjalankan hanya tes yang tidak bergantung db, suite pengujian saya menjalankan urutan besarnya lebih cepat! :)

Paul
sumber
6

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_dbmenggunakan 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
Rockallite
sumber
Saya telah menjalankan tes menggunakan pytest (dengan plugin pytest-django) dan NoDbTestRunner, jika entah bagaimana Anda membuat objek secara tidak sengaja di testcase dan Anda tidak menimpa nama database, objek akan dibuat di database lokal yang Anda siapkan di pengaturan. Nama 'NoDbTestRunner' harus 'NoTestDbTestRunner' karena itu tidak akan membuat database pengujian, tetapi akan menggunakan database Anda dari pengaturan.
Gabriel Muj
2

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
Tecuya
sumber
CATATAN: Jika Anda menghapus koneksi default dari daftar koneksi, Anda tidak akan dapat menggunakan model Django atau fitur lain yang biasanya menggunakan database (jelas kami tidak berkomunikasi dengan database tetapi Django memeriksa fitur berbeda yang didukung oleh DB) . Juga tampaknya koneksi._koneksi tidak mendukung __getitem__lagi. Gunakan connections._connections.defaultuntuk mengakses objek.
the_drow
2

Solusi lain adalah dengan membuat kelas tes Anda hanya mewarisi dari unittest.TestCasebukan 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.

Kurt Peek
sumber
Sepertinya ini masih membuat dan menghancurkan db, satu-satunya perbedaan adalah ia tidak menjalankan tes dalam transaksi dan tidak menyiram db.
Cam Rail
0

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

venkat
sumber
0

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_dbsaya berkomentar keluar cursor.execute("CREATE DATABASE ...garis dan menggantinya dengan passbegitutry blok tidak akan kosong.

Karena _destroy_test_dbsaya 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.

Chirael
sumber
0

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='[email protected]')
    self.assertEqual(user.username,'testuser')
  def test_foreign_key(self):
    user = User(username='testuser',email='[email protected]')
    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='[email protected]')
    ad = Classified(user=user,headline='headline',body='body')
    print(user.classified_set.first())
    self.assertTrue(True) #throws exception and never gets here
Simone
sumber
0

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.pyAnda 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
radtek
sumber