如何在Django中测试关闭数据库连接的方法?

时间:2016-08-30 01:27:19

标签: django django-testing django-database

我有一个使用Django ORM的长期运行的Python进程。它看起来像这样:

import django
from django.db import connection

from my_app import models


def my_process():
    django.setup()
    while (True):
        do_stuff()
        connection.close()
        models.MyDjangoModel().save()

有时do_stuff需要很长时间,此时我遇到了一个错误,我的MySql连接超时,因为数据库服务器将连接作为空闲终止。添加connection.close()行会强制django每次都获得一个新连接并修复该问题。 (见https://code.djangoproject.com/ticket/21597)。

但是,我正在使用django.test.TestCase测试此过程,并且调用connection.close会导致这些测试失败,因为django的TestCase类在事务中包装测试,并关闭连接在该事务中导致事务中断并引发django.db.transaction.TransactionManagementError

我尝试解决此问题的一种尝试是设置CONN_MAX_AGE数据库参数并调用connection.close_if_unusable_or_obsolete,但该事务还会更改连接的autocommit设置,而不是设置的默认值TrueFalse会导致close_if_unusable_or_obsolete尝试关闭连接(https://github.com/django/django/blob/master/django/db/backends/base/base.py#L497)。

我想我也可以在测试中模拟connection.close,所以它什么都不做,但这看起来有点像hacky。

测试需要关闭数据库连接的django方法的最佳方法是什么?

3 个答案:

答案 0 :(得分:7)

嗯,我不确定这是否是最好的答案,但由于这个问题到目前为止没有回复,我将发布我最终用于后代的解决方案:

我创建了一个辅助函数,在关闭连接之前检查我们当前是否处于原子块中:

import django
from django.db import connection

from my_app import models


def close_connection():
    """Closes the connection if we are not in an atomic block.

    The connection should never be closed if we are in an atomic block, as
    happens when running tests as part of a django TestCase. Otherwise, closing
    the connection is important to avoid a connection time out after long actions.     
    Django does not automatically refresh a connection which has been closed 
    due to idleness (this normally happens in the request start/finish part 
    of a webapp's lifecycle, which this process does not have), so we must
    do it ourselves if the connection goes idle due to stuff taking a really 
    long time.
    """
    if not connection.in_atomic_block:
        connection.close()


def my_process():
    django.setup()
    while (True):
        do_stuff()
        close_connection()
        models.MyDjangoModel().save()

正如评论所述,close_connection阻止了connection.close在测试中被调用,因此我们不再破坏测试事务。

答案 1 :(得分:0)

Django有一个TransactionTestCase类,与TestCase不同,它没有将测试代码封装在数据库事务中。希望这会有所帮助。

答案 2 :(得分:0)

您可以从TransactionTestCase继承Test类,而不是修改myprocess函数,而该类首先不会将测试函数包装在事务原子块中。如果您希望在测试函数中包含事务原子,则仅将其包装在需要的地方。

class Test(TransactionTestCase):
     def test_function(self):
         do_stuff()
         with transaction.atomic():
             your_transaction_atomic_test()
         my_process()
         do_other_stuff()

ps:从TestCase继承您的Test类将用事务原子块包装您的全部测试功能。