Bagaimana menjalankan basis data uji Django hanya dalam memori?

125

Tes unit Django saya membutuhkan waktu lama untuk dijalankan, jadi saya mencari cara untuk mempercepatnya. Saya sedang mempertimbangkan untuk menginstal SSD , tetapi saya tahu itu memiliki kelemahan juga. Tentu saja, ada hal-hal yang bisa saya lakukan dengan kode saya, tetapi saya sedang mencari perbaikan struktural. Bahkan menjalankan satu tes lambat karena database perlu dibangun kembali / dimigrasi ke selatan setiap waktu. Jadi, inilah ideku ...

Karena saya tahu database pengujian akan selalu sangat kecil, mengapa saya tidak bisa hanya mengkonfigurasi sistem untuk selalu menjaga seluruh database pengujian dalam RAM? Jangan pernah menyentuh disk sama sekali. Bagaimana cara mengkonfigurasi ini di Django? Saya lebih suka tetap menggunakan MySQL karena itulah yang saya gunakan dalam produksi, tetapi jika SQLite  3 atau yang lainnya membuat ini mudah, saya akan melakukannya.

Apakah SQLite atau MySQL memiliki opsi untuk berjalan sepenuhnya dalam memori? Seharusnya dimungkinkan untuk mengkonfigurasi disk RAM dan kemudian mengkonfigurasi database tes untuk menyimpan datanya di sana, tapi saya tidak yakin bagaimana cara memberitahu Django / MySQL untuk menggunakan direktori data yang berbeda untuk database tertentu, terutama karena itu terus terhapus. dan diciptakan kembali setiap menjalankan. (Saya menggunakan Mac FWIW.)

Leopd
sumber

Jawaban:

164

Jika Anda mengatur mesin basis data Anda ke sqlite3 saat Anda menjalankan tes, Django akan menggunakan basis data dalam memori .

Saya menggunakan kode seperti ini di saya settings.pyuntuk mengatur mesin ke sqlite ketika menjalankan tes saya:

if 'test' in sys.argv:
    DATABASE_ENGINE = 'sqlite3'

Atau di Django 1.2:

if 'test' in sys.argv:
    DATABASES['default'] = {'ENGINE': 'sqlite3'}

Dan akhirnya di Django 1.3 dan 1.4:

if 'test' in sys.argv:
    DATABASES['default'] = {'ENGINE': 'django.db.backends.sqlite3'}

(Path lengkap ke backend tidak sepenuhnya diperlukan dengan Django 1.3, tetapi membuat pengaturan maju kompatibel.)

Anda juga dapat menambahkan baris berikut, jika Anda mengalami masalah dengan migrasi Selatan:

    SOUTH_TESTS_MIGRATE = False
Etienne
sumber
9
Ya persis. Aku seharusnya memasukkan itu ke dalam jawabanku! Gabungkan bahwa dengan SOUTH_TESTS_MIGRATE = Salah dan pengujian Anda harus jauh lebih cepat.
Etienne
7
ini adalah mengagumkan. pada pengaturan Django yang lebih baru gunakan baris ini: 'ENGINE': 'sqlite3' jika 'tes' di sys.argv lain 'Django.db.backends.mysql',
mjallday
3
@ Thomas Zielinski - Hmm, itu tergantung apa yang Anda uji. Tetapi saya sepenuhnya setuju bahwa, pada akhirnya dan dari waktu ke waktu, Anda perlu menjalankan tes dengan database asli Anda (Postgres, MySQL, Oracle ...). Tetapi menjalankan tes Anda di memori dengan sqlite dapat menghemat banyak waktu.
Etienne
3
Saya membalikkan -1 ke +1: seperti yang saya lihat sekarang, ini jauh lebih cepat untuk menggunakan sqlite untuk menjalankan cepat dan beralih ke MySQL untuk misalnya tes harian akhir. (Perhatikan bahwa saya harus melakukan edit tiruan untuk membuka kunci pemungutan suara)
Tomasz Zieliński
12
Perhatian dengan ini "test" in sys.argv; mungkin memicu ketika Anda tidak menginginkannya, misalnya manage.py collectstatic -i test. sys.argv[1] == "test"adalah kondisi yang lebih tepat yang seharusnya tidak memiliki masalah itu.
keturn
83

Saya biasanya membuat file pengaturan terpisah untuk pengujian dan menggunakannya dalam perintah uji misalnya

python manage.py test --settings=mysite.test_settings myapp

Ini memiliki dua manfaat:

  1. Anda tidak perlu memeriksa testatau kata ajaib seperti itu di sys.argv, test_settings.pybisa saja

    from settings import *
    
    # make tests faster
    SOUTH_TESTS_MIGRATE = False
    DATABASES['default'] = {'ENGINE': 'django.db.backends.sqlite3'}

    Atau Anda dapat mengubah lebih lanjut untuk kebutuhan Anda, memisahkan pengaturan pengujian dengan bersih dari pengaturan produksi.

  2. Manfaat lain adalah Anda dapat menjalankan pengujian dengan mesin basis data produksi alih-alih sqlite3 menghindari bug yang halus, jadi sambil mengembangkan penggunaan

    python manage.py test --settings=mysite.test_settings myapp

    dan sebelum melakukan kode dijalankan sekali

    python manage.py test myapp

    hanya untuk memastikan bahwa semua tes benar-benar lulus.

Anurag Uniyal
sumber
2
Saya suka pendekatan ini. Saya memiliki banyak file pengaturan yang berbeda dan menggunakannya untuk lingkungan server yang berbeda, tetapi saya tidak berpikir untuk menggunakan metode ini untuk memilih database pengujian yang berbeda. Terima kasih untuk idenya.
Alexis Bellido
Halo Anurag, saya mencoba ini tetapi database saya yang lain yang disebutkan dalam pengaturan juga dijalankan. Saya tidak dapat menemukan alasan yang tepat.
Bhupesh Pant
Jawaban bagus. Saya bertanya-tanya bagaimana cara menentukan file pengaturan saat menjalankan tes melalui cakupan.
Wtower
Ini pendekatan yang bagus, tetapi tidak KERING. Django sudah tahu bahwa Anda sedang menjalankan tes. Jika Anda bisa 'menghubungkan ke' pengetahuan ini entah bagaimana, Anda akan siap. Sayangnya, saya percaya itu memerlukan perpanjangan perintah manajemen. Mungkin masuk akal untuk membuat generik ini dalam inti kerangka kerja, dengan, misalnya memiliki pengaturan MANAGEMENT_COMMAND yang disetel ke perintah saat ini setiap kali manage.py dipanggil, atau sesuatu dengan efek itu.
DylanYoung
2
@DylanYoung Anda dapat membuatnya kering dengan memasukkan pengaturan utama ke dalam test_settings dan hanya mengganti hal-hal yang Anda inginkan untuk pengujian.
Anurag Uniyal
22

MySQL mendukung mesin penyimpanan yang disebut "MEMORY", yang dapat Anda konfigurasi di konfigurasi database Anda ( settings.py) seperti:

    'USER': 'root',                      # Not used with sqlite3.
    'PASSWORD': '',                  # Not used with sqlite3.
    'OPTIONS': {
        "init_command": "SET storage_engine=MEMORY",
    }

Perhatikan bahwa mesin penyimpanan MEMORY tidak mendukung kolom blob / teks, jadi jika Anda menggunakan django.db.models.TextFieldini tidak akan bekerja untuk Anda.

muudscope
sumber
5
+1 untuk menyebutkan kurangnya dukungan untuk kolom blob / teks. Tampaknya juga tidak mendukung transaksi ( dev.mysql.com/doc/refman/5.6/en/memory-storage-engine.html ).
Tuukka Mustonen
Jika Anda benar-benar ingin tes dalam memori, Anda mungkin lebih baik menggunakan sqlite yang setidaknya mendukung transaksi.
atomic77
15

Saya tidak bisa menjawab pertanyaan utama Anda, tetapi ada beberapa hal yang dapat Anda lakukan untuk mempercepatnya.

Pertama, pastikan bahwa database MySQL Anda diatur untuk menggunakan InnoDB. Kemudian dapat menggunakan transaksi untuk mengembalikan keadaan db sebelum setiap tes, yang menurut pengalaman saya telah menyebabkan peningkatan besar-besaran. Anda dapat melewatkan perintah init database di settings.py Anda (sintaks Django 1.2):

DATABASES = {
    'default': {
            'ENGINE':'django.db.backends.mysql',
            'HOST':'localhost',
            'NAME':'mydb',
            'USER':'whoever',
            'PASSWORD':'whatever',
            'OPTIONS':{"init_command": "SET storage_engine=INNODB" } 
        }
    }

Kedua, Anda tidak perlu menjalankan migrasi Selatan setiap kali. Diatur SOUTH_TESTS_MIGRATE = Falsedalam settings.py Anda dan basis data akan dibuat dengan syncdb biasa, yang akan jauh lebih cepat daripada menjalankan semua migrasi historis.

Daniel Roseman
sumber
Tip yang bagus! Itu mengurangi tes saya dari 369 tests in 498.704smenjadi 369 tests in 41.334s . Ini lebih dari 10 kali lebih cepat!
Gabi Purcaru
Apakah ada saklar yang setara dalam pengaturan.py untuk migrasi di Django 1.7+?
Edward Newell
@ EdwardNewell Tidak juga. Tetapi Anda dapat menggunakan --keepuntuk mempertahankan database dan tidak memerlukan set lengkap migrasi Anda untuk diterapkan kembali pada setiap percobaan. Migrasi baru masih akan berjalan. Jika Anda sering berpindah antar cabang, mudah untuk masuk ke kondisi tidak konsisten (Anda dapat mengembalikan migrasi baru sebelum Anda beralih dengan mengubah database ke database pengujian dan menjalankannya migrate, tetapi ini sedikit menyebalkan).
DylanYoung
10

Anda dapat melakukan tweaker ganda:

  • gunakan tabel transaksional: keadaan perlengkapan awal akan ditetapkan menggunakan rollback basis data setelah setiap TestCase.
  • letakkan dir data database Anda di ramdisk: Anda akan mendapatkan sejauh yang berkaitan dengan pembuatan database dan juga menjalankan tes akan lebih cepat.

Saya menggunakan kedua trik dan saya cukup senang.

Cara mengaturnya untuk MySQL di Ubuntu:

$ sudo service mysql stop
$ sudo cp -pRL /var/lib/mysql /dev/shm/mysql

$ vim /etc/mysql/my.cnf
# datadir = /dev/shm/mysql
$ sudo service mysql start

Hati-hati, ini hanya untuk pengujian, setelah reboot database Anda dari memori hilang!

Potr Czachur
sumber
Terima kasih! bekerja untukku. Saya tidak dapat menggunakan sqlite, karena saya menggunakan fitur khusus untuk mysql (indeks teks lengkap). Untuk pengguna ubuntu, Anda harus mengedit konfigurasi apparmor Anda untuk mengizinkan akses mysqld ke / dev / shm / mysql
Ivan Virabyan
Sorakan untuk kepala Ivan dan Potr. Nonaktifkan profil mysql AppArmor untuk saat ini, tetapi menemukan panduan untuk menyesuaikan profil lokal yang relevan: blogs.oracle.com/jsmyth/entry/apparmor_and_mysql
trojjer
Hmm. Saya sudah mencoba menyesuaikan profil lokal untuk memberikan akses mysqld ke jalur / dev / shm / mysql dan isinya, tetapi layanan hanya dapat mulai dalam mode 'mengeluh' (perintah aa-complain) dan tidak 'menegakkan', untuk beberapa alasan ... Pertanyaan untuk forum lain! Yang tidak bisa saya pahami adalah bagaimana tidak ada 'keluhan' sama sekali ketika itu bekerja, menyiratkan bahwa mysqld tidak melanggar profil ...
trojjer
4

Pendekatan lain: minta instance MySQL lain berjalan di tempfs yang menggunakan RAM Disk. Petunjuk dalam posting blog ini: Mempercepat MySQL untuk pengujian di Django .

Keuntungan:

  • Anda menggunakan database yang persis sama dengan yang digunakan server produksi Anda
  • tidak perlu mengubah konfigurasi mysql default Anda
neves
sumber
2

Memperluas jawaban Anurag, saya menyederhanakan proses dengan membuat test_settings yang sama dan menambahkan berikut ini ke manage.py

if len(sys.argv) > 1 and sys.argv[1] == "test":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.test_settings")
else:
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")

tampaknya lebih bersih karena sys sudah diimpor dan manage.py hanya digunakan melalui baris perintah, jadi tidak perlu mengacaukan pengaturan

Alvin
sumber
2
Perhatian dengan ini "test" in sys.argv; mungkin memicu ketika Anda tidak menginginkannya, misalnya manage.py collectstatic -i test. sys.argv[1] == "test"adalah kondisi yang lebih tepat yang seharusnya tidak memiliki masalah itu.
keturn
2
@keturn dengan cara ini menghasilkan pengecualian ketika berjalan ./manage.pytanpa argumen (mis. untuk melihat plugin mana yang tersedia, sama seperti --help)
Antony Hatchkins
1
@AntonyHatchkins Itu sepele untuk dipecahkan:len(sys.argv) > 1 and sys.argv[1] == "test"
DylanYoung
1
@DylanYoung Ya, itulah yang saya ingin Alvin tambahkan ke solusinya, tetapi dia tidak terlalu tertarik untuk memperbaikinya. Pokoknya itu lebih mirip hack cepat daripada solusi yang sah.
Antony Hatchkins
1
belum melihat jawaban ini dalam beberapa saat, saya memperbarui potongan untuk mencerminkan peningkatan @ DylanYoung
Alvin
0

Gunakan di bawah ini di setting.py

DATABASES['default']['ENGINE'] = 'django.db.backends.sqlite3'
Ehsan Barkhordar
sumber