You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django场景下如何测试cron定时任务执行时的并发冲突问题

Django定时任务并发冲突测试实现方案

问题根因

你之前用线程测试时报数据库会话冲突的错误,是因为Django默认测试用例的数据库连接不会在子线程运行结束后自动释放,导致测试结束销毁测试库时检测到仍有活跃连接。同时默认TestCase的事务隔离特性会导致子线程无法读取主线程中未提交的测试数据。

方案1:多线程测试(最贴近真实场景)

实现要点

  • 测试类继承TransactionTestCase而非默认TestCase,关闭单测试事务隔离
  • 子线程运行结束后主动关闭所有数据库连接,释放会话
  • 启动两个线程同时执行任务,等待全部运行完成后做断言

完整代码

import threading
import time
from django.test import TransactionTestCase
from unittest.mock import patch
# 导入你自己的业务代码
from your_app.module import task, MyModel

class TaskConcurrencyTest(TransactionTestCase):
    @patch("your_app.module.api_call")
    def test_no_duplicate_update_when_concurrent(self, mock_api_call):
        # 构造测试数据
        test_obj = MyModel.objects.create(foo=10, task_ran=False, bar=None)

        # 模拟API延迟响应
        def api_side_effect(foo_val):
            time.sleep(2)
            return foo_val + 1
        mock_api_call.side_effect = api_side_effect

        # 封装带连接清理的任务执行函数
        def run_task():
            try:
                task()
            finally:
                from django.db import connections
                connections.close_all()

        # 同时启动两个任务
        t1 = threading.Thread(target=run_task)
        t2 = threading.Thread(target=run_task)
        t1.start()
        t2.start()
        # 等待两个任务执行完成
        t1.join()
        t2.join()

        # 验证API仅调用一次,数据无重复更新
        mock_api_call.assert_called_once_with(10)
        test_obj.refresh_from_db()
        self.assertEqual(test_obj.bar, 11)
        self.assertTrue(test_obj.task_ran)

方案2:单线程模拟并发(更轻量,无数据库连接问题)

不需要真实启动多线程,在第一次API调用的时机手动触发第二次任务执行,模拟前一个任务未完成、后一个任务启动的场景,更适合单元测试:

from django.test import TestCase
from unittest.mock import patch
from your_app.module import task, MyModel

class TaskConcurrencyTest(TestCase):
    @patch("your_app.module.api_call")
    def test_no_duplicate_update_when_concurrent(self, mock_api_call):
        test_obj = MyModel.objects.create(foo=10, task_ran=False, bar=None)
        call_count = 0

        def api_side_effect(foo_val):
            nonlocal call_count
            call_count += 1
            # 第一次调用API时触发第二次任务执行,模拟并发场景
            if call_count == 1:
                task()
            return foo_val + 1
        mock_api_call.side_effect = api_side_effect

        task()

        mock_api_call.assert_called_once_with(10)
        test_obj.refresh_from_db()
        self.assertEqual(test_obj.bar, 11)
        self.assertTrue(test_obj.task_ran)

注意:上述两种方案验证的前提是你已经对task函数做了并发控制(比如行锁、分布式锁、select_for_update等),如果你的原task代码没有加控制,测试会不通过。

内容的提问来源于stack exchange,提问作者andreashhp

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 18:54:08