如何仅在内存中运行Django的测试数据库?


125

我的Django单元测试需要很长时间才能运行,因此我正在寻找加快速度的方法。我正在考虑安装SSD,但我知道它也有缺点。当然,我的代码可以做一些事情,但是我正在寻找结构上的修复方法。由于每次都需要重建/向南迁移数据库,因此即使运行单个测试也很慢。所以这是我的主意...

由于我知道测试数据库总是很小,所以为什么不能仅将系统配置为始终将整个测试数据库保留在RAM中?绝对不要触摸磁盘。如何在Django中配置它?我宁愿继续使用MySQL,因为这是我在生产中使用的方式,但是如果使用SQLite  3或其他方法可以简化这一点,我会采用这种方式。

SQLite或MySQL是否可以选择完全在内存中运行?应该可以配置RAM磁盘,然后配置测试数据库以将其数据存储在其中,但是我不确定如何告诉Django / MySQL为特定数据库使用不同的数据目录,特别是因为它不断被删除并重新创建每次运行。(我在Mac FWIW上。)

Answers:


164

如果在运行测试时将数据库引擎设置为sqlite3,则Django将使用内存数据库

settings.py在运行测试时使用如下代码将引擎设置为sqlite:

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

或在Django 1.2中:

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

最后在Django 1.3和1.4中:

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

(到Django 1.3并不一定要有完整的后端路径,但是可以使设置向前兼容。)

您还可以添加以下行,以防南向迁移出现问题:

    SOUTH_TESTS_MIGRATE = False

9
对,就是这样。我应该把它放在我的答案中!结合使用SOUTH_TESTS_MIGRATE = False和您的测试应该快得多。
艾蒂安(Etienne)2010年

7
真棒。在较新的django设置上,使用以下行:'ENGINE':'sqlite3',如果在sys.argv中执行'test',否则在'django.db.backends.mysql'中,
mjallday

3
@Tomasz Zielinski-嗯,这取决于您要测试的内容。但我完全同意,最终需要时不时地使用真实的数据库(Postgres,MySQL,Oracle ...)运行测试。但是,使用sqlite在内存中运行测试可以节省大量时间。
艾蒂安

3
我将-1改为+1:正如我现在所看到的,使用sqlite进行快速运行并切换到MySQL进行最终日常测试要快得多。(请注意,我必须做一个虚拟编辑才能解锁投票)
TomaszZieliński

12
注意这个"test" in sys.argv; 它可能在您不希望触发时触发,例如manage.py collectstatic -i testsys.argv[1] == "test"是一个更精确的条件,应该不会出现该问题。
keturn

83

我通常为测试创建一个单独的设置文件,并在测试命令中使用它,例如

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

它有两个好处:

  1. 您不必检查testsys.argv中的任何此类神奇词,test_settings.py只需

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

    或者,您可以根据需要进一步调整它,将测试设置与生产设置完全分开。

  2. 另一个好处是您可以使用生产数据库引擎而不是sqlite3进行测试,从而避免了细微的错误,因此在开发使用时

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

    并在提交代码之前运行一次

    python manage.py test myapp

    只是为了确保所有测试都通过了。


2
我喜欢这种方法。我有一堆不同的设置文件,并将它们用于不同的服务器环境,但是我没有考虑过使用这种方法选择不同的测试数据库。谢谢你的主意。
Alexis Bellido

嗨,Anurag,我尝试过此操作,但设置中提到的其他数据库也已执行。我无法找出确切原因。
Bhupesh Pant 2014年

好答案。我不知道在通过coverage运行测试时如何指定设置文件。
Wtower

这是一个好方法,但不是干的。Django已经知道您正在运行测试。如果您能够以某种方式“了解”这些知识,那么您会被设置的。不幸的是,我认为这需要扩展管理命令。最好在框架的核心中使用此通用名称,例如,每当调用manage.py时,将设置MANAGEMENT_COMMAND设置为当前命令,或达到某种效果。
DylanYoung '17

2
@DylanYoung您可以通过将主要设置包含在test_settings中,并覆盖您要测试的内容来使其干燥。
阿努拉格

22

MySQL支持称为“ MEMORY”的存储引擎,您可以在数据库config(settings.py)中对其进行配置,如下所示:

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

请注意,MEMORY存储引擎不支持blob /文本列,因此,如果您使用django.db.models.TextField此功能,则将无法使用。


5
+1表示缺乏对Blob /文本列的支持。它似乎也不支持事务(dev.mysql.com/doc/refman/5.6/en/memory-storage-engine.html)。
Tuukka Mustonen

如果您真的想要进行内存中测试,最好使用至少支持事务的sqlite。
atomic77

15

我无法回答您的主要问题,但是您可以做一些事情来加快速度。

首先,请确保您的MySQL数据库已设置为使用InnoDB。然后,它可以使用事务在每次测试之前回滚db的状态,以我的经验,这导致了极大的提速。您可以在settings.py中传递数据库init命令(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" } 
        }
    }

其次,您不需要每次都运行South迁移。设置SOUTH_TESTS_MIGRATE = False在你的settings.py和数据库将与普通的执行syncdb,这将是比通过所有历史悠久的迁移运行更快创建。


大提示!它使我的测试从减少369 tests in 498.704s369 tests in 41.334s 。这快十倍以上!
加比·普卡鲁

在settings.py中是否有等效的开关可以在Django 1.7+中进行迁移?
爱德华·纽厄尔2015年

@EdwardNewell不完全是。但是,您可以使用它--keep来持久化数据库,而无需在每次测试运行时重新应用整套迁移。新迁移仍将运行。如果您经常在分支之间切换,则很容易进入不一致状态(您可以在切换之前通过将数据库更改为测试数据库并运行来还原新的迁移migrate,但这有点麻烦)。
DylanYoung '17

10

您可以进行两次调整:

  • 使用事务表:在每个TestCase之后,将使用数据库回滚来设置初始固定装置状态。
  • 将您的数据库数据目录放在ramdisk上:就数据库创建而言,您将获得很多收益,并且运行测试会更快。

我正在使用这两种技巧,我很高兴。

如何在Ubuntu上为MySQL设置它:

$ 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

当心,这只是为了测试,从内存中重新启动数据库后,丢失了!


谢谢!为我工作。我不能使用sqlite,因为我正在使用特定于mysql(全文索引)的功能。对于ubuntu用户,您必须编辑apparmor配置以允许mysqld访问/ dev / shm / mysql
Ivan Virabyan 2011年

为Ivan和Potr欢呼鼓舞。目前已禁用AppArmor mysql配置文件,但找到了自定义相关本地配置文件的指南:blogs.oracle.com/jsmyth/entry/apparmor_and_mysql
trojjer 2013年

嗯 我尝试自定义本地配置文件,以使mysqld访问/ dev / shm / mysql路径及其内容,但是对于某些用户,该服务只能以“ complain”模式(aa-complain命令)启动,而不能“ enforce”启动原因...另一个论坛的问题!我不明白的是,当它工作时,根本没有“投诉”,这意味着mysqld没有违反配置文件...
trojjer


2

通过扩展Anurag的答案,我通过创建相同的test_settings并将以下内容添加到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")

似乎更干净,因为sys已经导入并且manage.py仅通过命令行使用,因此无需弄乱设置


2
注意这个"test" in sys.argv; 它可能在您不希望触发时触发,例如manage.py collectstatic -i testsys.argv[1] == "test"是一个更精确的条件,应该不会出现该问题。
keturn

2
@keturn这样,它在./manage.py没有参数的情况下运行时会生成异常(例如,查看--help
与之

1
@AntonyHatchkins要解决的问题很简单:len(sys.argv) > 1 and sys.argv[1] == "test"
DylanYoung

1
@DylanYoung是的,这正是我想要Alvin添加到他的解决方案中的内容,但是他对改进它并不特别感兴趣。无论如何,与合法解决方案相比,它看起来更像是一种快速破解。
安东尼·哈奇金斯

1
有一阵子没看这个答案了,我更新了代码片段以反映@DylanYoung的改进
Alvin

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.