pytest--解决 scope=session 的 fixture 在多进程运行情况下仍然只运行一次

发布时间 2023-03-30 14:15:34作者: 测试-13

前言

在多进程的情况下,每个子进程都会是一个session,里面都会执行一次session级别的fixture,那么如果有些数据是随机生成的,比如生成token、密钥等数据,那么在多进程执行,不同进程里面,得到的token或者密钥值那就不一致了。就可能导致用例执行的失败,那么直接举个例子吧

 

示例

比如随机生成token:工程目录结构如下:

 根目录下test_case.py: 

import pytest

#参数化,传入参数
@pytest.mark.parametrize('n',list(range(3)))
def test_get_info(login,n):
    print('===获取用户个人信息===',n)
    name,token = login
    print(f'用户名:{name},token:{token}')

 根目录下conftest.py:

import random
import pytest

@pytest.fixture(scope='session')
def login():
    name = 'admin'
    token = str(random.random())
    return name,token

test_baidu文件夹下test_case1.py:

import pytest

@pytest.mark.parametrize('n',list(range(2)))
def test_case1_1(open_baidu,n):print('===baidu 执行测试用例test_case1_1====',n)

@pytest.mark.parametrize('n',list(range(2)))
def test_case1_2(open_baidu,n):print('===baidu 执行测试用例test_case1_2====',n)

test_baidu文件夹下test_case2.py:

def test_01():
    print('====1===========')
class Test_01:
    def test_02(self):
        print('=======2=============')

test_baidu文件夹下conftest.py:

import pytest

@pytest.fixture(scope='module')
def open_baidu(login):
    name,token = login
    print(f'用户{name}打开了百度,token:{token}')

 使用pytest-xdist多进程执行测试用例,与html报告搭配查看输出。只使用3个进程去跑9个测试用例

根目录执行命令:pytest -n 3--html=report.html

输出结果如下:

 可以看到分配了3个进程去跑,随机得到3种token值,也就是证明了scope='session'级别的login的fixture被调用了3次,所以才会产生3种token值

但这与我们所希望的不符合,因为单进程去跑,session级别的fixture只会在所有用例执行前后才会运行一次。

 

实现session级别的fixture在多进程中只运行一次

 实现思路:如果是分布式运行的话,使用FileLock来给缓存文件上锁,第一个进程调用写入数据后,其余进程也可以访问该缓存文件,但只读取,就可以保证所有进程得到同样的数据。如果是单进程运行,那么可以判断worker_id是否是“admin”,如果是的话,照常生成token,不会出现session级别的fixture运行多次的问题

那么接下来就对根目录下的conftest.py进行调整:

import json
import os
import random

import pytest
from filelock import FileLock


@pytest.fixture(scope='session')
#tmp_path_factory是一个session级别的fixture,每次执行只会创建一个临时目录
def login(tmp_path_factory,worker_id):
    name = 'admin'
    #单机运行,则运行这里代码
    if worker_id == 'master':
        token = str(random.random())
        print(f'token:{token}')
        #写入os的环境变量中
        os.environ['token'] = token
        return name,token

    # 分布式运行
    # 获取所有子节点共享的临时目录
    print(tmp_path_factory)
    root_tmp_dir = tmp_path_factory.getbasetemp().parent
    print(root_tmp_dir)
    fn = root_tmp_dir / 'data.json'
    print(fn)
    #FileLock来给缓存文件上锁,第一个进程调用写入数据后,其余进程也可以访问该缓存文件,但只读取,就可以保证所有进程得到同样的数据
    with FileLock(str(fn) + '.lock'):
        if fn.is_file():
            #缓存文件中读取数据,例如token
            token = json.loads(fn.read_text())
            print(f'读取缓存文件,token:{token}')
        else:
            #第一次生成缓存文件
            token = str(random.random())
            print(f'fixture请求登陆端口,获取token:{token}')
            fn.write_text(json.dumps(token))
            print(f'首次执行,token:{token}')
        #token保存os的环境变量
        os.environ['token'] = token
    return name,token

 输出结果:

可以看到所有的token值都固定了,也就证明token生成只执行了一次,并写入到缓存文件中,剩下的进程则是去读取缓存文件中的token值。并不是所有的测试用例都会去读取缓存文件,而是根据进程去访问缓存文件。这也同样做到了所有进程下读取到数据是一致的