Pytest系列(30)- 使用 pytest-xdist 分布式插件,如何保证 scope=session 的 fixture 在多进程运行情况下仍然能只运行一次

简介: Pytest系列(30)- 使用 pytest-xdist 分布式插件,如何保证 scope=session 的 fixture 在多进程运行情况下仍然能只运行一次

如果你还想从头学起Pytest,可以看看这个系列的文章哦!

https://www.cnblogs.com/poloyy/category/1690628.html

 

背景


  • 使用 pytest-xdist 分布式插件可以加快运行,充分利用机器多核 CPU 的优势
  • 将常用功能放到 fixture,可以提高复用性和维护性
  • 做接口自动化测试的时候,通常我们会将登录接口放到 fixture 里面,并且 scope 会设置为 session,让他全局只运行一次
  • 但是当使用 pytest-xdist 的时候,scope=session 的 fixture 无法保证只运行一次,官方也通报了这一问题

 

官方描述


  • pytest-xdist 的设计使每个工作进程将执行自己的测试集合并执行所有测试子集,这意味着在不同的测试过程中,要求高级范围的 fixture(如:session)将会被多次执行,这超出了预期,在某些情况下可能是不希望的
  • 尽管 pytest-xdist 没有内置支持来确保  scope=session 的fixture 仅执行一次,但是可以通过使用锁定文件进行进程间通信来实现

 

前置知识


pytest-xdist 分布式插件使用详细教程

https://www.cnblogs.com/poloyy/p/12694861.html

 

pytest-xdist 分布式插件原理

https://www.cnblogs.com/poloyy/p/12703290.html

 

fixture 的使用详细教程

https://www.cnblogs.com/poloyy/p/12642602.html

官方文档

https://pypi.org/project/pytest-xdist/

 

官方解决办法(直接套用就行)


import json
import pytest
from filelock import FileLock
@pytest.fixture(scope="session")
def session_data(tmp_path_factory, worker_id):
    if worker_id == "master":
        # not executing in with multiple workers, just produce the data and let
        # pytest's fixture caching do its job
        return produce_expensive_data()
    # get the temp directory shared by all workers
    root_tmp_dir = tmp_path_factory.getbasetemp().parent
    fn = root_tmp_dir / "data.json"
    with FileLock(str(fn) + ".lock"):
        if fn.is_file():
            data = json.loads(fn.read_text())
        else:
            data = produce_expensive_data()
            fn.write_text(json.dumps(data))
    return data


  • 若某个 scope = session 的 fixture 需要确保只运行一次的话,可以用上面的方法,直接套用,然后改需要改的部分即可(这个后面详细讲解)
  • 官方原话:这项技术可能并非在每种情况下都适用,但对于许多情况下,它应该是一个起点,在这种情况下,对于 scope = session 的fixture 只执行一次很重要

 

后续栗子的代码


项目结构

xdist+fixture(文件夹)
│  tmp(存放 allure 数据文件夹)
│  conftest.py
│  test_1.py
│  test_2.py
│  test_3.py
│ __init__.py │ 


test_1.py 代码

import os
def test_1(test):
    print("os 环境变量",os.environ['token'])
    print("test1 测试用例", test)


test_2.py 代码

import os
def test_2(test):
    print("os 环境变量",os.environ['token'])
    print("test2 测试用例", test)


test_3.py 代码

import os
def test_3(test):
    print("os 环境变量",os.environ['token'])
    print("test3 测试用例", test)


未解决情况下的栗子


conftest.py 代码

import os
import pytest
from random import random
@pytest.fixture(scope="session")
def test():
    token = str(random())
    print("fixture:请求登录接口,获取token", token)
    os.environ['token'] = token
    return token


运行命令

pytest -n 3 --alluredir=tmp

 

运行结果

image.png

image.png

scope=session 的 fixture 很明显执行了三次,三个进程下的三个测试用例得到的数据不一样,明显不会是我们想要的结果

 

使用官方解决方法的栗子


#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
__title__  = 
__Time__   = 2021/4/27 11:28
__Author__ = 小菠萝测试笔记
__Blog__   = https://www.cnblogs.com/poloyy/
"""
import json
import os
import pytest
from random import random
from filelock import FileLock
@pytest.fixture(scope="session")
def test(tmp_path_factory, worker_id):
    # 如果是单机运行 则运行这里的代码块【不可删除、修改】
    if worker_id == "master":
        """
        【自定义代码块】
        这里就写你要本身应该要做的操作,比如:登录请求、新增数据、清空数据库历史数据等等
        """
        token = str(random())
        print("fixture:请求登录接口,获取token", token)
        os.environ['token'] = token
        # 如果测试用例有需要,可以返回对应的数据,比如 token
        return token
    # 如果是分布式运行
    # 获取所有子节点共享的临时目录,无需修改【不可删除、修改】
    root_tmp_dir = tmp_path_factory.getbasetemp().parent
    # 【不可删除、修改】
    fn = root_tmp_dir / "data.json"
    # 【不可删除、修改】
    with FileLock(str(fn) + ".lock"):
        # 【不可删除、修改】
        if fn.is_file():
            # 缓存文件中读取数据,像登录操作的话就是 token 【不可删除、修改】
            token = json.loads(fn.read_text())
            print(f"读取缓存文件,token 是{token} ")
        else:
            """
            【自定义代码块】
            跟上面 if 的代码块一样就行
            """
            token = str(random())
            print("fixture:请求登录接口,获取token", token)
            # 【不可删除、修改】
            fn.write_text(json.dumps(token))
            print(f"首次执行,token 是{token} ")
        # 最好将后续需要保留的数据存在某个地方,比如这里是 os 的环境变量
        os.environ['token'] = token
    return token


运行命令

pytest -n 3 --alluredir=tmp

 

运行结果

image.png

image.png


可以看到 fixture 只执行了一次,不同进程下的测试用例共享一个数据 token

 

重点

  • 读取缓存文件并不是每个测试用例都会读,它是按照进程来读取的
  • 比如 -n 3 指定三个进程运行,那么有一个进程会执行一次 fixture(随机),另外两个进程会各读一次缓存
  • 假设每个进程有很多个用例,那也只是读一次缓存文件,而不会读多次缓存文件
  • 所以最好要将从缓存文件读出来的数据保存在特定的地方,比如上面代码的 os.environ 可以将数据保存在环境变量中

 

两个进程跑三个测试用例文件


还是上面栗子的代码

运行命令

pytest -n 2 --alluredir=tmp

 

运行结果

image.png

image.png

可以看到 test_3 的测试用例就没有读缓存文件了,每个进程只会读一次缓存文件,记住哦!

相关文章
|
4月前
|
Linux Python
linux上根据运行程序的进程号,查看程序所在的绝对路径。linux查看进程启动的时间
linux上根据运行程序的进程号,查看程序所在的绝对路径。linux查看进程启动的时间
75 2
|
2月前
|
存储 缓存 NoSQL
分布式架构下 Session 共享的方案
【10月更文挑战第15天】在实际应用中,需要根据具体的业务需求、系统架构和性能要求等因素,选择合适的 Session 共享方案。同时,还需要不断地进行优化和调整,以确保系统的稳定性和可靠性。
|
3月前
|
Linux Shell
6-9|linux查询现在运行的进程
6-9|linux查询现在运行的进程
|
3月前
|
存储 NoSQL Java
使用springSession完成分布式session
本文介绍了如何使用 `spring-session` 实现分布式 Session 管理,并提供了将 Session 存储在 Redis 中的具体配置示例。通过配置相关依赖及 Spring 的配置文件,可以轻松实现 Session 的分布式存储。示例中详细展示了所需的 Maven 依赖、Spring 配置及过滤器配置,并给出了启动项目后在 Redis 中查看 Session 数据的方法。
|
2月前
|
NoSQL Linux 程序员
进程管理与运行分析
进程管理与运行分析
27 0
|
4月前
|
存储 NoSQL Java
一天五道Java面试题----第十一天(分布式架构下,Session共享有什么方案--------->分布式事务解决方案)
这篇文章是关于Java面试中的分布式架构问题的笔记,包括分布式架构下的Session共享方案、RPC和RMI的理解、分布式ID生成方案、分布式锁解决方案以及分布式事务解决方案。
一天五道Java面试题----第十一天(分布式架构下,Session共享有什么方案--------->分布式事务解决方案)
|
4月前
|
数据采集 监控 API
如何监控一个程序的运行情况,然后视情况将进程杀死并重启
这篇文章介绍了如何使用Python的psutil和subprocess库监控程序运行情况,并在程序异常时自动重启,包括多进程通信和使用日志文件进行断点重续的方法。
|
4月前
|
存储 分布式计算 算法
探索Hadoop的三种运行模式:单机模式、伪分布式模式和完全分布式模式
在配置Hadoop集群之前,了解这三种模式的特点、适用场景和配置差异是非常重要的。这有助于用户根据个人需求和资源情况,选择最适合自己的Hadoop运行模式。在最初的学习和开发阶段,单机模式和伪分布式模式能为用户提供便利和成本效益。进而,当用户要处理大规模数据集时,完全分布式模式将是理想的选择。
298 2
|
4月前
|
消息中间件 JSON 自然语言处理
Python多进程日志以及分布式日志的实现方式
python日志模块logging支持多线程,但是在多进程下写入日志文件容易出现下面的问题: PermissionError: [WinError 32] 另一个程序正在使用此文件,进程无法访问。 也就是日志文件被占用的情况,原因是多个进程的文件handler对日志文件进行操作产生的。
|
4月前
|
Kubernetes Shell 测试技术
在Docker中,可以在一个容器中同时运行多个应用进程吗?
在Docker中,可以在一个容器中同时运行多个应用进程吗?