Jak uruchomić testową bazę danych Django tylko w pamięci?

125

Moje testy jednostkowe Django trwają długo, więc szukam sposobów, aby to przyspieszyć. Rozważam zainstalowanie dysku SSD , ale wiem, że ma to też swoje wady. Oczywiście są rzeczy, które mógłbym zrobić z moim kodem, ale szukam poprawki strukturalnej. Nawet wykonanie pojedynczego testu jest powolne, ponieważ za każdym razem należy odbudować bazę danych / migrować na południe. Oto mój pomysł ...

Ponieważ wiem, że testowa baza danych zawsze będzie dość mała, dlaczego nie mogę po prostu skonfigurować systemu tak, aby zawsze przechowywać całą testową bazę danych w pamięci RAM? Nigdy nie dotykaj dysku. Jak to skonfigurować w Django? Wolałbym nadal używać MySQL, ponieważ tego właśnie używam w produkcji, ale jeśli SQLite  3 lub coś innego ułatwi to, pójdę w ten sposób.

Czy SQLite lub MySQL mają opcję uruchamiania w całości w pamięci? Powinno być możliwe skonfigurowanie dysku RAM, a następnie skonfigurowanie testowej bazy danych tak, aby przechowywała tam swoje dane, ale nie jestem pewien, jak powiedzieć Django / MySQL, aby używał innego katalogu danych dla określonej bazy danych, zwłaszcza że jest ona ciągle wymazywana i odtwarzałem każdy bieg. (Jestem na komputerze Mac FWIW.)

Leopd
źródło

Odpowiedzi:

164

Jeśli podczas wykonywania testów ustawisz silnik bazy danych na sqlite3, Django użyje bazy danych w pamięci .

Używam takiego kodu w moim, settings.pyaby ustawić silnik na sqlite podczas wykonywania moich testów:

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

Lub w Django 1.2:

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

I wreszcie w Django 1.3 i 1.4:

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

(Pełna ścieżka do zaplecza nie jest bezwzględnie konieczna w Django 1.3, ale sprawia, że ​​ustawienie jest zgodne z kolejnymi wersjami).

Możesz również dodać następujący wiersz, jeśli masz problemy z migracjami na południe:

    SOUTH_TESTS_MIGRATE = False
Etienne
źródło
9
Tak, dokładnie. Powinienem był to umieścić w mojej odpowiedzi! Połącz to z SOUTH_TESTS_MIGRATE = False, a twoje testy powinny być dużo szybsze.
Etienne
7
to jest niesamowite. w nowszych konfiguracjach django użyj tej linii: 'ENGINE': 'sqlite3' if 'test' in sys.argv else 'django.db.backends.mysql',
mjallday
3
@Tomasz Zieliński - Hmm, to zależy od tego, co testujesz. Ale całkowicie się zgadzam, że na koniec i od czasu do czasu musisz przeprowadzić testy ze swoją prawdziwą bazą danych (Postgres, MySQL, Oracle ...). Ale uruchamianie testów w pamięci za pomocą sqlite może zaoszczędzić dużo czasu.
Etienne
3
Odwracam -1 do +1: tak jak teraz widzę, znacznie szybciej jest używać sqlite do szybkich uruchomień i przełączam się na MySQL np. Do ostatecznych codziennych testów. (Zauważ, że musiałem zrobić fikcyjną edycję, aby odblokować głosowanie)
Tomasz Zieliński
12
Ostrożnie z tym "test" in sys.argv; może się uruchomić, gdy tego nie chcesz, np manage.py collectstatic -i test. sys.argv[1] == "test"jest dokładniejszym warunkiem, który nie powinien mieć tego problemu.
keturn
83

Zwykle tworzę osobny plik ustawień do testów i używam go w poleceniu testowym np

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

Ma dwie zalety:

  1. Nie musisz sprawdzać, testczy takie magiczne słowo w sys.argv test_settings.pymoże po prostu być

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

    Lub możesz dodatkowo dostosować go do swoich potrzeb, wyraźnie oddzielając ustawienia testowe od ustawień produkcyjnych.

  2. Kolejną korzyścią jest to, że możesz uruchomić test z produkcyjnym silnikiem bazy danych zamiast sqlite3, unikając subtelnych błędów, więc podczas programowania

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

    a przed zatwierdzeniem kodu uruchom raz

    python manage.py test myapp

    tylko po to, aby mieć pewność, że wszystkie testy naprawdę zdały.

Anurag Uniyal
źródło
2
Podoba mi się to podejście. Mam kilka różnych plików ustawień i używam ich w różnych środowiskach serwerowych, ale nie myślałem o użyciu tej metody do wybrania innej testowej bazy danych. Dzięki za pomysł.
Alexis Bellido,
Cześć Anurag, próbowałem tego, ale moje inne bazy danych wymienione w ustawieniach również są wykonywane. Nie jestem w stanie znaleźć dokładnego powodu.
Bhupesh Pant
Niezła odpowiedź. Zastanawiam się, jak określić plik ustawień podczas przeprowadzania testów przez pokrycie.
Wtower
To dobre podejście, ale nie SUCHE. Django już wie, że prowadzisz testy. Gdybyś mógł jakoś „podpiąć się” do tej wiedzy, byłbyś nastawiony. Niestety uważam, że wymaga to rozszerzenia dowództwa kierownictwa. Prawdopodobnie miałoby sens zrobienie tego generycznego w rdzeniu frameworka, na przykład poprzez ustawienie ustawienia MANAGEMENT_COMMAND na bieżące polecenie za każdym razem, gdy wywoływany jest manage.py, lub coś w tym stylu.
DylanYoung
2
@DylanYoung możesz go wysuszyć, włączając główne ustawienia do test_settings i po prostu zastępując rzeczy, które chcesz przetestować.
Anurag Uniyal
22

MySQL obsługuje mechanizm magazynowania o nazwie „MEMORY”, który można skonfigurować w konfiguracji bazy danych ( settings.py) jako taki:

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

Pamiętaj, że silnik magazynu MEMORY nie obsługuje kolumn blob / text, więc jeśli używasz django.db.models.TextFieldtego, nie zadziała.

muudscope
źródło
5
+1 za wzmiankę o braku obsługi kolumn typu blob / text. Wydaje się również, że nie obsługuje transakcji ( dev.mysql.com/doc/refman/5.6/en/memory-storage-engine.html ).
Tuukka Mustonen
Jeśli naprawdę chcesz testów w pamięci, prawdopodobnie lepiej będzie wybrać sqlite, który przynajmniej obsługuje transakcje.
atomic77
15

Nie mogę odpowiedzieć na twoje główne pytanie, ale jest kilka rzeczy, które możesz zrobić, aby przyspieszyć działanie.

Po pierwsze, upewnij się, że Twoja baza danych MySQL jest skonfigurowana do korzystania z InnoDB. Następnie może użyć transakcji do przywrócenia stanu bazy danych przed każdym testem, co z mojego doświadczenia doprowadziło do ogromnego przyspieszenia. Możesz przekazać polecenie init bazy danych w swoim pliku settings.py (składnia 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" } 
        }
    }

Po drugie, nie musisz za każdym razem uruchamiać migracji na południe. Ustaw SOUTH_TESTS_MIGRATE = Falsew swoim settings.py, a baza danych zostanie utworzona przy użyciu zwykłej syncdb, co będzie znacznie szybsze niż wykonanie wszystkich historycznych migracji.

Daniel Roseman
źródło
Świetna wskazówka! Zmniejszyło to moje testy z 369 tests in 498.704sdo 369 tests in 41.334s . To ponad 10 razy szybciej!
Gabi Purcaru
Czy istnieje równoważny przełącznik w settings.py dla migracji w Django 1.7+?
Edward Newell,
@EdwardNewell Niezupełnie. Możesz jednak użyć --keepdo utrwalenia bazy danych i nie wymagać ponownego stosowania całego zestawu migracji przy każdym uruchomieniu testowym. Nowe migracje będą nadal działać. Jeśli często przełączasz się między gałęziami, łatwo jest jednak uzyskać niespójny stan (możesz cofnąć nowe migracje przed przełączeniem, zmieniając bazę danych na testową i uruchamiając migrate, ale jest to trochę uciążliwe).
DylanYoung
10

Możesz wykonać podwójne ulepszenia:

  • użyj tabel transakcyjnych: początkowy stan urządzeń zostanie ustawiony za pomocą wycofywania bazy danych po każdym TestCase.
  • umieść katalog danych swojej bazy danych na ramdysku: zyskasz dużo jeśli chodzi o tworzenie bazy danych, a także uruchomienie testu będzie szybsze.

Używam obu sztuczek i jestem całkiem zadowolony.

Jak skonfigurować go dla MySQL na 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

Uwaga, to tylko do testów, po ponownym uruchomieniu baza danych z pamięci zostaje utracona!

Potr Czachur
źródło
dzięki! pracuje dla mnie. Nie mogę używać sqlite, ponieważ używam funkcji specyficznych dla mysql (indeksy pełnotekstowe). Użytkownicy ubuntu będą musieli zmodyfikować konfigurację apparmor, aby umożliwić mysqld dostęp do / dev / shm / mysql
Ivan Virabyan
Pozdrowienia dla heads up Ivana i Potra. Na razie wyłączono profil mysql AppArmor, ale znalazłem przewodnik dotyczący dostosowywania odpowiedniego profilu lokalnego: blogs.oracle.com/jsmyth/entry/apparmor_and_mysql
trojjer
Hmm. Próbowałem dostosować profil lokalny, aby dać mysqld dostęp do ścieżki / dev / shm / mysql i jej zawartości, ale usługa może się uruchomić tylko w trybie „narzekania” (polecenie aa-narzeka), a nie „egzekwowania”, dla niektórych powód ... Pytanie na inne forum! Nie mogę zrozumieć, że nie ma żadnych
``
4

Inne podejście: uruchom inną instancję MySQL w tempfs, która używa dysku RAM. Instrukcje w tym wpisie na blogu: Przyspieszenie MySQL do testowania w Django .

Zalety:

  • Używasz dokładnie tej samej bazy danych, z której korzysta Twój serwer produkcyjny
  • nie ma potrzeby zmieniania domyślnej konfiguracji mysql
neves
źródło
2

Rozszerzając odpowiedź Anuraga, uprościłem proces, tworząc te same ustawienia test_settings i dodając następujące do 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")

wydaje się czystszy, ponieważ sys jest już zaimportowany, a manage.py jest używany tylko z wiersza poleceń, więc nie ma potrzeby zaśmiecania ustawień

Alvin
źródło
2
Ostrożnie z tym "test" in sys.argv; może się uruchomić, gdy tego nie chcesz, np manage.py collectstatic -i test. sys.argv[1] == "test"jest dokładniejszym warunkiem, który nie powinien mieć tego problemu.
keturn
2
@keturn w ten sposób generuje wyjątek, gdy działa ./manage.pybez argumentów (np. aby zobaczyć, które wtyczki są dostępne, tak samo jak --help)
Antony Hatchkins
1
@AntonyHatchkins To trywialne do rozwiązania:len(sys.argv) > 1 and sys.argv[1] == "test"
DylanYoung
1
@DylanYoung Tak, właśnie to chciałem, aby Alvin dodał do swojego rozwiązania, ale nie jest szczególnie zainteresowany jego ulepszaniem. W każdym razie wygląda to bardziej na szybki hack niż legalne rozwiązanie.
Antony Hatchkins
1
nie patrzyłem na tę odpowiedź od jakiegoś czasu, zaktualizowałem fragment, aby odzwierciedlić poprawę @ DylanYoung
Alvin,
0

Użyj poniżej w swoim setting.py

DATABASES['default']['ENGINE'] = 'django.db.backends.sqlite3'
Ehsan Barkhordar
źródło