Django 单元测试等待数据库

Lon*_*Dev 5 python django unit-testing

我有一个 Django 命令,它运行一个循环,直到数据库可用:

import time

from django.db import connections
from django.db.utils import OperationalError
from django.core.management.base import BaseCommand


class Command(BaseCommand):
    """Django command to pause execution until database is available"""

    def handle(self, *args, **options):
        """Handle the command"""
        self.stdout.write('Waiting for database...')
        db_conn = None
        while not db_conn:
            try:
                db_conn = connections['default']
            except OperationalError:
                self.stdout.write('Database unavailable, waiting 1 second...')
                time.sleep(0.1)

        self.stdout.write(self.style.SUCCESS('Database available!'))
Run Code Online (Sandbox Code Playgroud)

我想为此代码创建单元测试。

我已经设法从一开始就测试可用的数据库,如下所示:

def test_wait_for_db_ready(self):
    """Test waiting for db when db is available"""

    with patch('django.db.utils.ConnectionHandler.__getitem__') as gi:
        gi.return_value = True
        call_command('wait_for_db')
        self.assertTrue(True)
Run Code Online (Sandbox Code Playgroud)

有没有办法测试命令在返回之前等待数据库可用?

到目前为止,我已经尝试了以下操作,但是它不起作用,因为attemptgetitem.

def test_wait_for_db(self):
    """Test waiting for db"""
    attempt = 0

    def getitem(alias):
        if attempt < 5:
            attempt += 1
            raise OperationalError()
        else:
            return True

    with patch('django.db.utils.ConnectionHandler.__getitem__') as gi:
        gi.side_effect = getitem
        call_command('wait_for_db')
        self.assertGreaterEqual(attempt, 5)
Run Code Online (Sandbox Code Playgroud)

Wil*_*ing 5

有几种方法可以实现这一点。最简单的方法可能只是放弃getitem()嵌套函数并使用OperationalErrors序列设置副作用。然后,您可以使用修补gi对象的call_count. 例如:

def test_wait_for_db(self):
    """Test waiting for db"""

    with patch('django.db.utils.ConnectionHandler.__getitem__') as gi:
        gi.side_effect = [OperationalError] * 5 + [True]
        call_command('wait_for_db')
        self.assertGreaterEqual(gi.call_count, 5)  # Verify using the call_count
Run Code Online (Sandbox Code Playgroud)

如果您希望保留该getitem()函数,那么我认为您只需要创建attempt变量nonlocal即可在嵌套函数中看到它:

def test_wait_for_db(self):
    """Test waiting for db"""
    attempt = 0

    def getitem(alias):
        nonlocal attempt  # Make the outer attempt variable visible
        if attempt < 5:
            attempt += 1
            raise OperationalError()
        else:
            return True

    with patch('django.db.utils.ConnectionHandler.__getitem__') as gi:
        gi.side_effect = getitem
        call_command('wait_for_db')
        self.assertGreaterEqual(attempt, 5)
Run Code Online (Sandbox Code Playgroud)

第三,正如评论中所建议的,您可以创建一个具有attempt属性的类,并使用该类的实例作为副作用:

def test_wait_for_db(self):
    """Test waiting for db"""

    class Getitem:
        def __init__(self):
            self.attempt = 0

        def __call__(self, item):
            if self.attempt < 5:
                self.attempt += 1
                raise OperationalError()
            else:
                return True

    with patch('django.db.utils.ConnectionHandler.__getitem__') as gi:
        getitem = Getitem()
        gi.side_effect = getitem
        call_command('wait_for_db')
        self.assertGreaterEqual(getitem.attempt, 5)  # Access the attempts from the instance
Run Code Online (Sandbox Code Playgroud)