Django的override_settings装饰器在并行启动测试时是如何工作的?
为什么
override_settings在并行测试下不会出现你推导的竞态 核心认知偏差的来源
你之前的所有推导,都基于一个不成立的前提:所有并行运行的测试用例,共享同一个全局的settings单例对象。
如果真的是同一个内存地址的settings实例,被多个并行执行流同时修改、同时读取,那你说的"后执行的测试覆盖值会污染其他测试读取结果"的问题必然会发生——但Django从设计之初,就根本不是在这个场景下实现的并行。
Django并行测试的实际运行逻辑
- 开启
--parallel参数跑测试时,Django的并行实现是多进程隔离的独立子进程拆分用例,完全不是单进程内多线程共享内存的并行模式 - 主进程启动时会预先创建N个独立的工作子进程,每个子进程都有自己完全独立的内存空间、独立的Python解释器实例、从父进程拷贝出来的独立
settings全局单例 - 主进程会把所有测试用例拆分成队列,挨个分发给空闲的子进程执行;每个子进程拿到自己负责的用例列表后,是单线程顺序执行完队列内的所有用例,不会在子进程内部再开多线程交叉跑测试
- 对应你举的三个测试用例的实际运行场景:
- 主进程拆分完用例,把
test_1分配给子进程A,test_2分配给子进程B,test_3分配给子进程C - 三个子进程内存完全隔离,互相看不到对方的内存变量:
- 子进程A执行
test_1时,会在自己进程内部把settings.SETTING覆盖成1,执行完测试逻辑就把值恢复成初始加载的原始配置 - 子进程B执行
test_2时,会在自己进程内部把settings.SETTING覆盖成2,这个修改完全不会传递到其他子进程的内存空间 - 子进程C全程没有对自己进程内的
settings做任何修改,读取到的永远是初始化时加载的原始配置值
- 子进程A执行
- 主进程拆分完用例,把
- 这种模式下,你担心的"test_2还没等test_1打印就把共享值改成2"的场景根本不可能出现——两个测试的
settings对象从物理上就不在同一块内存里
什么情况下你推导的竞态才会真的出现
如果你脱离Django默认的测试运行器,自己在同一个Python进程内、用多线程的方式交叉跑测试(比如自定义了基于多线程的测试runner、或者把第三方测试工具配置成同进程多线程执行模式),那你推导的竞态是真实存在的:
override_settings的实现里,没有做任何线程本地存储(TLS)的适配,也没有加线程锁保护- 只要同一个进程内有两个执行流同时修改共享的
settings._wrapped属性,就一定会出现值被意外覆盖的问题——因为这个实现从设计目标上,就从来没有考虑过同进程多线程的测试执行场景
单进程内部的执行逻辑为什么完全通顺
就算是单个子进程内部顺序跑多个带override_settings的测试,因为是单线程顺序执行:执行测试前先enable修改对应设置值,测试逻辑执行完立刻disable把值恢复成原来的副本,整个过程没有执行流的交叉,和你之前理解的顺序执行逻辑完全一致,不存在任何交叉修改的可能。
内容的提问来源于stack exchange,提问作者josearr
相关产品推荐
相关产品推荐

