接口测试06 -- pytest接口自动化封装&Loggin实战

1. 接口关键字封装

1.1 基本概念

接口关键字封装是指:将接口测试过程中常用的操作、验证封装成可复用的关键字(或称为函数、方法),以提高测试代码的可维护性和可复用性。

1.2 常见的接口关键字封装方式

1. 发送请求:封装一个函数,接受参数如请求方法、URL、请求头、请求体等,使用相应的库发送请求,如requests库或HttpClient库。
---------------->>>>
2. 响应验证:封装一个函数,接受参数如响应对象、预期结果等,通过断言或其他方式验证响应的状态码、响应体等是否符合预期。
---------------->>>>
3. 数据提取:封装一个函数,接受参数如响应体、提取表达式等,使用正则表达式XPath、JSONPath等方式提取需要的数据,并返回提取结果。
---------------->>>>
4. 数据保存:封装一个函数,接受参数如文件路径、数据等,将数据保存到指定
的文件中,如Excel、CSV、数据库等。
---------------->>>>
5. 参数化配置:封装一个函数,接受参数如环境配置、数据文件路径等,根据不
同的环境或数据文件读取对应的配置信息,如接口URL、认证信息等。
---------------->>>>
封装的目的:
通过封装接口关键字,可以使接口测试代码更简洁、可读性更高,并且提供了灵活性和扩展性,方便维护和管理测试代码。

2. 接口封装实战应用

2.1 接口关键字封装代码示例 

这是接口关键字驱动类,用于提供自动化接口测试的关键字方法。
主要是实现常用的关键字内容,并定义好所有的参数内容即可
接口中常用关键字:
1.各种模拟请求方法:Post/get/put/delete/header/....
2.集合Allure,可添加@allure.step,这样在自动化执行的时候,Allure报告可以直接捕捉相关的执行信息,让测试报告更详细
3.根据需求进行断言封装:jsonpath、数据库断言。
----------------------->>>
封装get、post请求示例:
import requests
import jsonpath
import json

"""
关键字驱动/基类/工具类:
1. 发送请求:8种:post、get、put、delete...
2. 提取数据
补充知识点:方法的缺省值:params=None (默认值),如果没有传参则默认为None
"""

class ApiKey:
    def get(self, url, params=None, **kwargs):
        """
        发送get请求
        :param url:接口请求url
        :param params: 拼接在url的参数
        :param kwargs: 其它的参数
        :return: 返回请求后的数据
        """
        print(">>>>>>开始发送Get请求")
        return requests.get(url=url, params=params, **kwargs)

    def post(self, url, data=None, json=None, **kwargs):
        """
         发送post请求
        :param url: 接口请求url
        :param data: data的请求数据
        :param json: json的请求数据
        :param kwargs: 其它的参数
        :return: 返回请求后的数据
        """
        print(">>>>>>开始发送Post请求")
        res = requests.post(url=url, data=data, json=json, **kwargs)
        print(">>>>>>响应数据为:", res.json())
        return res

2.2 封装实现完整的登录接口

基于上面的封装代码:

实现一个项目的登录接口(post请求)、并添加josnpath提取响应信息

import requests
import jsonpath
import json


class ApiKey:

    def post(self, url, data=None, json=None, **kwargs):
        """
         发送post请求
        :param url: 接口请求url
        :param data: data的请求数据
        :param json: json的请求数据
        :param kwargs: 其它的参数
        :return: 返回请求后的数据
        """
        print(">>>>>>开始发送Post请求")
        res = requests.post(url=url, data=data, json=json, **kwargs)
        print(">>>>>>响应数据为:", res.json())
        return res

    def get_text(self, response, key):
        """
        提取json当中的某个值
        :param response: 需要提取的json数据,比如:{"msg":"登录成功"}
        :param key: 对应要提取的jsonpath,比如: $.msg
        :return: 返回提取数据之后的【第一个值】
        """

        print(">>>>>>开始提取JsonPath响应数据")
        value_list = jsonpath.jsonpath(response, key)
        print(">>>>>>提取数据响应为:", value_list[0])
        return value_list[0]


# 主函数:
if __name__ == '__main__':
    # 1. 实例化对象:ApiKey
    ak = ApiKey()

    # 2. 通过对应的类调用对应的方法 --四要素
    url = "http://xxx.com/index.php?s=/api/user/login"

    # 公共参数
    pulic_data = {"application": "app", "application_client_type": "weixin"}

    # 请求参数-- body (你的body数据是要以json进行提交,参数:json)
    data = {"accounts": "hailey", "pwd": "hailey123", "type": "username"}
    res = ak.post(url=url, params=pulic_data, data=data)

    # 3. 提取数据
    text = ak.get_text(res.json(), "$.msg")

为了代码的健壮性,添加提取数据的格式判断:

如果返回的响应数据格式是json字符串"{"msg":"登录成功"}",则进行类型转换:

import requests
import jsonpath
import json


class ApiKey:

    def post(self, url, data=None, json=None, **kwargs):
        """
         发送post请求
        :param url: 接口请求url
        :param data: data的请求数据
        :param json: json的请求数据
        :param kwargs: 其它的参数
        :return: 返回请求后的数据
        """
        print(">>>>>>开始发送Post请求")
        res = requests.post(url=url, data=data, json=json, **kwargs)
        print(">>>>>>响应数据为:", res.json())
        return res

    def get_text(self, response, key):
        """
        提取json当中的某个值
        :param response: 需要提取的json数据,比如:{"msg":"登录成功"}
        :param key: 对应要提取的jsonpath,比如: $.msg
        :return: 返回提取数据之后的【第一个值】
        """

        # 为了代码的健壮性,添加判断是否为字符串(防止响应信息格式为json字符串)
        if isinstance(response,str):
            #是字符串,我就让它转一下类型
            response = json.loads(response)

        print(">>>>>>开始提取JsonPath响应数据")
        value_list = jsonpath.jsonpath(response, key)
        print(">>>>>>提取数据响应为:", value_list[0])
        return value_list[0]


# 主函数:
if __name__ == '__main__':
    # 1. 实例化对象:ApiKey
    ak = ApiKey()

    res ='{"msg":"登录成功"}'
    text = ak.get_text(res, "$.msg")

2.3 测试用例封装

这里,我们使用pytest框架进行测试用例的编写与封装

在之前Pytest的学习中,我们了解到:

需要将测试用例新建一个项目文件,一个是用例主体,放测试用例,另一主体是用例运行main文件;之前pytest使用详情戳:pytest -- 基本使用详解_pytest 具体能做哪些事情-CSDN博客pytest -- 进阶使用详解_怎么通过pytest-html插件查看pytest结果-CSDN博客

现在我们在项目文件下新增一个已经封装好的登录功能文件(即上面的示例代码)

 文件结构

测试用例 

在testcase文件下,首先新建一个'test_ds_01.py'文件(登陆的测试用例):

# 导入:从项目根目录的下一级开始写
from Requests_demo.day06.PytestFrame.api_keyword.api_key import ApiKey


# 方法:要以test 【默认的规则】
def test_loging():
    # 1. 实例化对象:ApiKey
    ak = ApiKey()

    # 2. 通过对应的类调用对应的方法 --四要素
    url = "http://xxxx.com/index.php?s=/api/user/login"
    pulic_data = {"application": "app", "application_client_type": "weixin"}
    data = {"accounts": "hailey", "pwd": "hailey123", "type": "username"}

    # 3. 发送请求
    res = ak.post(url=url, params=pulic_data, data=data)

    # 4. 提取数据
    text = ak.get_text(res.json(), "$.msg")

    # 5. 进行断言
    assert text == "登录成功", "期望结果和实际结果不一致"


"""
扩展(不建议):
如果你在当前的文件去进行调试的话,需要把运行模式调整成:unittest

为什么要调整:因为我们pytest的一些运行规则unittest不支持
但是,我们用的是pytest ,它支持的是通过文件的入口去调试
"""
# test_loging()

用例调试_ 扩展知识(错误示范)

不建议直接在测试用例模块中直接运行调试(直接调试就是把上面代码'test_loging()'这行放开):
运行后控制台会显示左右两块面板,它是uinttest的运行模式
因为我们用的是pytest ,它支持的是通过文件的入口去调试(项目文件下的main_run文件)

仅作为扩展,不建议:
如果你在当前的文件去进行调试的话,需要把运行模式调整成:unittest
为什么要调整?因为我们pytest的一些运行规则unittest不支持

unittest模块切换方式如下:

设置完,再来运行,就变成一块面板了:

正确姿势:通过main文件运行用例

# file name:main.run.py
# 框架的运行入口

import pytest

if __name__ == '__main__':
    """
    默认找当前项目文件下test开头、test结尾的模块运行; ./表示当前目录
    也可以指定文件路径运行(当前项目下其他用例模块),例如:(["-vs","./testcase/test_ds_022.py"])
    """
    pytest.main(["-vs"])

2.4 Pytest + Allure生成测试报告

这里我们使用Pytest + Allure生成测试报告,首先需要安装pytest、alluer的相关包

具体可以看我之前的文章:

pytest测试框架-- 基本功能使用详解_pytest 具体能做哪些事情-CSDN博客

pytest进阶 -- 结合Html / Allure生成测试报告_怎么通过pytest-html插件查看pytest结果-CSDN博客

代码示例 

只要按照搞定了安装,基于上面的代码,只需要在main文件中加两三行代码就能实现:

# 框架的运行入口

import pytest
import os

if __name__ == '__main__':
    """
    默认找当前项目文件下test开头、test结尾的模块运行; ./(当前目录)
    也可以指定文件路径运行,例如:(["-vs","./testcase/test_ds_022.py"])
    """
    # pytest.main(["-vs"])

    """
    通过pytest运行,并生成alluer报告:
    第一步:运行指定文件,运行结果的数据放在./result;--clena-alluerdir(每次运行之前自动清空历史数据)
    """
    pytest.main(["-vs", "./testcase","--alluredir", "./result", "--clean-alluredir"])

    # 第二步:将运行结果生成测试报告,固定写法如下:
    os.system("allure generate ./result -o ./report_allure --clean")

第二步的代码,也可以直接在pycharm的Terminal终端直接输入命令:

pytest -vs --alluredir=./result --clean-alluredir ./testcase

我还是觉得直接写在main文件中更加方面,不然每次都得手动敲一遍~...

运行后,在当前项目文件下就多了result、report_allure两个文件 

测试报告:

2.5 Allure报告优化

测试步骤优化

通过添加对应的测试步骤:
@allure.step("步骤内容注释")
@allure.title("测试用例标题")

① 封装的登录模块添加步骤注释:

即:api_keyword/api_key.py模块

每个方法的上面都加上@allure.step:

# file name:api_key.py
import allure
import requests
import jsonpath
import json

"""
关键字驱动/基类/工具类:
1. 发送请求:8种:post、get、put、delete...
2. 提取数据

补充知识点:方法的缺省值:params=None (默认值),如果没有传参则默认为None
"""
class ApiKey:
    @allure.step(">>>>>>开始发送Get请求")
    def get(self, url, params=None, **kwargs):
        """
        发送get请求
        :param url:接口请求url
        :param params: 拼接在url的参数
        :param kwargs: 其它的参数
        :return: 返回请求后的数据
        """
        return requests.get(url=url, params=params, **kwargs)

    @allure.step(">>>>>>开始发送Post请求")
    def post(self, url, data=None, json=None, **kwargs):
        """
         发送post请求
        :param url: 接口请求url
        :param data: data的请求数据
        :param json: json的请求数据
        :param kwargs: 其它的参数
        :return: 返回请求后的数据
        """
        res = requests.post(url=url, data=data, json=json, **kwargs)
        print(">>>>>>响应数据是:", res.json())
        return res

    @allure.step(">>>>>>开始提取响应数据")
    def get_text(self, response, key):
        """
        提取json当中的某个值
        :param response: 需要提取的json数据,比如:{"msg":"登录成功"}
        :param key: 对应要提取的jsonpath,比如: $.msg
        :return: 返回提取数据之后的【第一个值】
        """
        if isinstance(response,str):
            # 如果响应数据是字符串,我就让它转一下类型
            response = json.loads(response)

        value_list = jsonpath.jsonpath(response, key)
        print(">>>>>>提取的响应数据是:", value_list[0])
        return value_list[0]

② 测试用例:test_ds_01.py

添加@allure.title("测试用例标题")

import allure

# 导入:从项目根目录的下一级开始写
from Requests_demo.day06.PytestFrame.api_keyword.api_key import ApiKey


# 方法:要以test 【默认的规则】
@allure.title("登陆接口_测试用例")
def test_loging():
    # 1. 实例化对象:ApiKey
    ak = ApiKey()

    # 2. 通过对应的类调用对应的方法 --四要素
    url = "http://xxx.com/index.php?s=/api/user/login"
    pulic_data = {"application": "app", "application_client_type": "weixin"}
    data = {"accounts": "hailey", "pwd": "hailey123", "type": "username"}

    # 3. 发送请求
    res = ak.post(url=url, params=pulic_data, data=data)

    # 4. 提取数据
    text = ak.get_text(res.json(), "$.msg")

    # 5. 进行断言
    assert text == "登录成功", "期望结果和实际结果不一致"

重新执行main文件,打开测试报告,就会显示测试用例的名称、步骤:

增加了测试报告的可阅读性

接口关联优化

接口关联,指的是上下游依赖的接口,放在同一个测试用例中进行执行

这里测试项目背景是某电商平台:

所以第一个接口是上面提到的登录,通过登录后拿到token进行第二个接口:加入购物车

file name:test_ds_02.py

import allure

# 导入:从项目根目录的下一级开始写
from Requests_demo.day06.PytestFrame.api_keyword.api_key import ApiKey


@allure.title("登录测试成功并且加入购物车")
def test_addcard():
    with allure.step("第一步:登录接口的调用"):
        # 1. 实例化对象:ApiKey
        ak = ApiKey()

        # 2. 通过对应的类调用对应的方法 --四要素
        url = "http://xxx.com/index.php?s=/api/user/login"
        pulic_data = {"application": "app", "application_client_type": "weixin"}
        data = {"accounts": "hailey", "pwd": "hailey123", "type": "username"}

        # 3. 发送请求
        res = ak.post(url=url, params=pulic_data, data=data)

        # 4. 提取数据
        text = ak.get_text(res.json(), "$.msg")
        token = ak.get_text(res.json(), "$..token")

        # 5. 进行断言
        assert text == "登录成功", "期望结果和实际结果不一致"

    with allure.step("第二步:加入购物车接口调用"):
        # 接口二: 加入购物车
        url = "http://xxx.com/index.php?s=/api/cart/save"
        public_data = {"application": "app", "application_client_type": "weixin", "token": token}
        data = {
            "goods_id": "11",
            "spec": [
                {
                    "type": "尺寸",
                    "value": "M"
                }
            ],
            "stock": "10"
        }
        res = ak.post(url=url, params=public_data, data=data)

        # 获取响应数据
        result = res.json()
        print(f"响应结果是:{result}")
        # 期望结果
        desire_res = "加入成功"

        # 实际结果
        reality_res = ak.get_text(result, "$.msg")
        assert desire_res == reality_res, "期望结果和实际结果不一致"

因为新建了一个用例模块,所以main文件,稍稍改动,指定‘test_ds_02.py’文件运行

import pytest
import os

if __name__ == '__main__':
    pytest.main(["-vs", "./testcase/test_ds_02.py","--alluredir", "./result", "--clean-alluredir"])
    os.system("allure generate ./result -o ./report_allure --clean")

执行后,打开测试报告:


2.6 接口关联封装改进

设置全局变量

可以通过设置全局变量去实现, 仅在当前文件下有效(不推荐)
先声明,后赋值
语法:global 变量名

给第一个方法添加全局变量: 

# file name:test_ds_03.py

import allure

# 导入:从项目根目录的下一级开始写
from Requests_demo.day06.PytestFrame.api_keyword.api_key import ApiKey


@allure.title("登录测试成功")
def test_login():
    global ak, token   # 设置全局变量
    with allure.step("第一步:登录接口的调用"):
        ak = ApiKey()
        url = "http://xxx.com/index.php?s=/api/user/login"
        pulic_data = {"application": "app", "application_client_type": "weixin"}
        data = {"accounts": "hailey", "pwd": "hailey123", "type": "username"}

        res = ak.post(url=url, params=pulic_data, data=data)
        text = ak.get_text(res.json(), "$.msg")
        token = ak.get_text(res.json(), "$..token")
        assert text == "登录成功", "期望结果和实际结果不一致"


@allure.title("加入购物车")
def test_addcard():
    with allure.step("第二步:加入购物车接口调用"):
        # 接口二: 加入购物车
        url = "http://xxx.com/index.php?s=/api/cart/save"
        public_data = {"application": "app", "application_client_type": "weixin", "token": token}
        data = {
            "goods_id": "11",
            "spec": [
                {
                    "type": "尺寸",
                    "value": "M"
                }
            ],
            "stock": "10"
        }
        res = ak.post(url=url, params=public_data, data=data)

        result = res.json()
        print(f"响应结果是:{result}")

        desire_res = "加入成功"
        reality_res = ak.get_text(result, "$.msg")
        assert desire_res == reality_res, "期望结果和实际结果不一致"

重新运行main文件,查看测试报告:

项目级token多文件关联

fixture+conftest实现项目级的token多文件关联:

在软件测试中,Fixture 是一种用于管理测试环境和测试数据的机制。它允许在测试函数或方法运行之前和之后执行特定的代码,以确保测试的可重复性和一致性。
Fixture主要用于设置测试环境、准备测试数据、执行清理操作等,以便能够按预期运行。
=====================
语法结构:
@pytest.fixture() 装饰器在 pytest 测试框架中用于定义测试夹具(fixture),并可以指定夹具的作用域。pytest 提供了四种作用域选项:
1. function (默认):作用范围限于单个测试函数。每个测试函数执行前都会调用该夹具。
2. class :作用范围限于测试类中的所有测试方法。每个测试类执行前都会调用该夹具。
3. module :作用范围限于单个测试模块。每个测试模块执行前都会调用该夹具。
4. session :作用范围限于整个测试会话。在整个测试会话期间只会调用一次该夹具。
------------>>
简单来说:就是某个变量,在整个项目的全局都需要用到,就可以利用 pytest的测试夹具去进行实现---固定的命令conftest.py
通过在 @pytest.fixture() 装饰器中使用 scope 参数来指定作用域,例如:
def my_fixture():
# 夹具的具体实现
    ...
需要根据具体的需求和测试场景选择适当的作用域。
默认情况下, @pytest.fixture() 的作用域是 function ,适用于大多数情况。

代码示例:

测试夹具:conftest,相当于把登陆接口、token作为整个项目的全局变量

#  file name:conftest.py(文件名固定,不能修改)

import pytest
from Requests_demo.day06.PytestFrame.api_keyword.api_key import ApiKey
"""
可以写具体的方法、正常的调用某个接口
pytest在运行的时候【会自动】先调用这个文件,后面还具体调用这个方法

方法:获取token
1. 正常的请求对应的接口并且提取数据
2. 告诉别人这个是一个测试夹具(测试前置、后置操作):@pytest.fixture()
"""

@pytest.fixture(scope= "session")
def token_fix():
    ak = ApiKey()
    url = "http://xxx.com/index.php?s=/api/user/login"
    pulic_data = {"application": "app", "application_client_type": "weixin"}
    data = {"accounts": "hailey", "pwd": "hailey123", "type": "username"}

    res = ak.post(url=url, params=pulic_data, data=data)
    token = ak.get_text(res.json(), "$..token")
    return token,ak

测试用例: 注意token,ak的前后顺序,要与conftest中的顺序一致,如不一致则会报错

# file name:test_ds-04.py
import allure


@allure.title("加入购物车")
def test_addcard(token_fix):
    token , ak = token_fix
    with allure.step("第二步:加入购物车接口调用"):
        # 接口二: 加入购物车
        url = "http://xxxx.com/index.php?s=/api/cart/save"
        public_data = {"application": "app", "application_client_type": "weixin", "token": token}
        data = {
            "goods_id": "11",
            "spec": [
                {
                    "type": "尺寸",
                    "value": "M"
                }
            ],
            "stock": "10"
        }
        res = ak.post(url=url, params=public_data, data=data)
        # ----------------------------------------
        # 获取响应数据
        result = res.json()
        print(f"响应结果是:{result}")
        # 期望结果
        desire_res = "加入成功31321312321"
        # 实际结果
        reality_res = ak.get_text(result, "$.msg")
        assert desire_res == reality_res, "期望结果和实际结果不一致"

main文件:

import pytest
import os

if __name__ == '__main__':
    pytest.main(["-v", "./testcase/test_ds_04.py","--alluredir", "./result", "--clean-alluredir"])
    os.system("allure generate ./result -o ./report_allure --clean")

重新运行main文件,查看测试报告:

全局常量定义

比较固定的变量,不会随便修改的,称之为常量。

比如:常用的服务IP、端口(环境变量)、公共参数等,数据链接信息、测试账号/密码等,统一放到一个专门的文件,定义为常量,方便维护修改。

===================

操作步骤
1. 新建一个py文件,专门用来存放常量,一般常量的命名都是大写;
2. 对应的文件导入这个模块,即可使用。
如下,新建一个config.py文件放在项目根目录下:

这里我把环境地址、测试账号/密码、请求的公共参数,全部进行参数化设置: 

# 配置常量的文件;file name:config.py

# 环境URL:
PROJECT_URL = "http://xxx.com/index.php"


# 测试账号
USERNAME = "hailey"
PASSWORD = "hailey123"
LOGINTYPE = "username"

# 请求公共参数:
PULIC_DATA =  {"application": "app", "application_client_type": "weixin"}

然后对应,修改conftest.py的代码:引用config文件的参数名即可

# file name:conftest.py

import pytest
from Requests_demo.day06.PytestFrame.api_keyword.api_key import ApiKey
from Requests_demo.day06.PytestFrame.config import * # 导入config文件所有内容

@pytest.fixture(scope= "session")
def token_fix():
    ak = ApiKey()
    url = PROJECT_URL+"?s=/api/user/login"
    pulic_data = PULIC_DATA
    data = {"accounts": USERNAME, "pwd": PASSWORD, "type": LOGINTYPE}

    res = ak.post(url=url, params=pulic_data, data=data)
    token = ak.get_text(res.json(), "$..token")
    return token, ak

重新运行,测试报告的结果和之前一样:

3. Logging入门详解

3.1 Logging基本概述

使用标准库提供的 logging API 最主要的好处是:
Python所有模块都可能参与日志输出,包括你自己的日志消息和第三方模块的日志消息。
===============================
记录程序日志信息的目的:
● 可以很方便的了解程序的运行情况
● 可以分析用户的操作行为、喜好等信息
● 方便开发人员检查bug(Allure就可以支持)
● 方便和运维系统对接,进行告警检测(必须输出日志文件,Allure无法解决,得用Logging)
===============================
官网文档链接:
Python日志官网: https://docs.python.org/zh-cn/3/library/logging.html
Pytest日志官网: https://docs.pytest.org/en/latest/how-to/logging.html

3.2 logging日志级别介绍

日志等级可以分为5个,从低到高分别是:
1.DEBUG
2.INFO
3.WARNING
4.ERROR
5.CRITICAL
===============================
日志等级从低到高的顺序是: DEBUG < INFO < WARNING < ERROR < CRITICAL
===============================
日志等级说明:
DEBUG:程序调试bug时使用
INFO:程序正常运行时使用
WARNING:程序未按预期运行时使用,但并不是错误,如:用户登录密码错误
ERROR:程序出错误时使用,如:IO操作失败
CRITICAL:特别严重的问题,导致程序不能再继续运行时使用,如:磁盘空间为空,一般很少使用
===============================
默认的是WARNING等级,当在WARNING或WARNING之上等级的才记录日志信息。

3.3 logging日志的使用

在 logging 包中记录日志的方式有两种:

1. 输出到控制台
2. 保存到日志文件

代码示例

import logging
# 设置全局日志级别为DEBUG
logging.basicConfig(level=logging.DEBUG)
logging.debug("debug信息")
logging.info("info信息")
logging.warning("warning信息")
logging.error("error信息")
logging.critical("critical信息")

3.4 logging日志格式使用大全

标识
描述
%(asctime)s
字符串形式的当前时间。
默认格式是 “2003-07-08 16:49:45,896”。逗号后面的是毫秒
%(levelname)s
打印日志级别名称
%(filename)s
打印当前执行程序名
%(lineno)d
打印日志的当前行号
%(message)s
打印日志信息
%(levelno)s
打印日志级别的数值
%(funcName)s
打印日志的当前函数
%(module)s
调用日志输出函数的模块名
%(pathname)s
打印当前执行程序的路径,其实就是sys.argv[0]
%(name)s
所使用的日志名称,默认是'root',因为默认使用的是 rootLogger
%(msecs)d
日志事件发生事件的毫秒部分
% (relativeCreated)d
输出日志信息时的,自Logger创建以 来的毫秒数
%(threadName)s
打印线程名称
%(thread)d
打印线程ID
%(process)d
打印进程ID

代码示例

"""
日志格式化:
%(asctime)s :字符串形式的当前时间。默认格式是 “2003-07-08 16:49:45,896”。逗号后面的是毫
秒
%(levelname)s : 打印日志级别名称
%(filename)s : 打印当前执行程序名
%(lineno)d : 打印日志的当前行号
%(message)s : 打印日志信息
"""
import logging
# 设置全局日志级别为DEBUG

logging.basicConfig(level=logging.DEBUG, format="%(asctime)s %(levelname)s %(filename)s(%(lineno)d) : %(message)s ")
logging.debug("debug信息".format())
logging.info("info信息")
logging.warning("warning信息")
logging.error("error信息")
logging.critical("critical信息")

3.5 logging日志写入文件

可以通过:
logging.basicConfig(level=logging.DEBUG, format="日志格 式",stream=file对象)

代码示例

import logging
file = open("log.log", mode="a", encoding="utf-8")
# 设置全局日志级别为DEBUG
logging.basicConfig(level=logging.DEBUG, format="%(asctime)s %(levelname)s %(filename)s(%(lineno)d) : %(message)s ",stream=file)
logging.debug("debug信息")
logging.info("info信息")
logging.warning("warning信息")
logging.error("error信息")
logging.critical("critical信息")

运行后,log文件信息如下:

3.6 logging日志封装

几个重要的概念
● Logger 记录器暴露了应用程序代码直接使用的接口。
● Handler 处理器将日志记录(由记录器创建)发送到适当的目标。
● Filter 过滤器提供了更细粒度的功能,用于确定要输出的日志记录。
● Formatter 格式器指定最终输出中日志记录的样式。

代码示例

import logging
import os
from logging.handlers import RotatingFileHandler

class Logger:
    def __init__(self, name, log_file, level=logging.DEBUG):
        self.logger = logging.getLogger(name)
        self.logger.setLevel(level)

        # 创建日志格式
        formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

        # 确保日志文件的目录存在
        log_dir = os.path.dirname(log_file)
        if not os.path.exists(log_dir):
            os.makedirs(log_dir)

        # 创建文件处理器
        file_handler = RotatingFileHandler(log_file, maxBytes=1048576, backupCount=3)
        file_handler.setFormatter(formatter)
        self.logger.addHandler(file_handler)

        # 创建控制台处理器
        console_handler = logging.StreamHandler()
        console_handler.setFormatter(formatter)
        self.logger.addHandler(console_handler)

if __name__ == '__main__':
    # 创建日志实例
    logger = Logger('my_logger', '.\\logdata\\log.log')
    # 输出日志
    logger.logger.debug('This is a debug message')
    logger.logger.info('This is an info message')
    logger.logger.warning('This is a warning message')
    logger.logger.error('This is an error message')
    logger.logger.critical('This is a critical message')


4. Pytest框架中的Logging实战

pytest日志的配置 - pytest.ini

1、pytest 是有日志的开关: pytest.ini

        不能改名字

        不要在文件里面里面写中文

        控制显示的格式及是否显示

日志配置代码 :

[pytest]
log_cli= true
log_level=NOTSET
log_format = %(asctime)s %(levelname)s %(message)s %(filename)s %(funcName)s %(lineno)d
log_date_format = %Y-%m-%d %H:%M:%S

log_file = ./logdata/log.log
log_file_level = info
log_file_format = %(asctime)s %(levelname)s %(message)s %(filename)s %(funcName)s %(lineno)d
log_file_date_format = %Y-%m-%d %H:%M:%S

代码解释: 

测试夹具配置 - conftest:

2. 每个测试用例都需要产生对应的日志信息:

● 测试夹具(固定的写法)、控制显示哪些字段

● yield 方法:它就是一个返回的作用,类似于return;

● 区别: return返回之后就直接回到调用的地方;

● vield:调用执行完毕之后会回到调用的地方(vield)

这里基于上面的conftest代码做修改,如下:

# file name:conftest.py(文件名固定,不能修改)

import pytest
import logging
from Requests_demo.day06.PytestFrame.api_keyword.api_key import ApiKey
from Requests_demo.day06.PytestFrame.config import *


@pytest.fixture(scope= "session")
def token_fix():
    ak = ApiKey()
    url = PROJECT_URL+"?s=/api/user/login"
    pulic_data = PULIC_DATA
    data = {"accounts": USERNAME, "pwd": PASSWORD, "type": LOGINTYPE}
    print("USERNAME的值:",USERNAME)

    res = ak.post(url=url, params=pulic_data, data=data)
    token = ak.get_text(res.json(), "$..token")
    return token, ak


# 当执行一个case的时候会自动的调用这个方法:把对应的数据传过来给到call
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
def pytest_runtest_makereport(item, call):
    # 通过 out = yield 定义了一个生成器。在生成器中,res = out.get_result() 获取了测试结果对象。
    out = yield

    res = out.get_result()
    #  res.when == "call":表示正在运行调用测试函数的阶段。
    if res.when == "call":
        logging.info(f"用例ID:{res.nodeid}")
        logging.info(f"测试结果:{res.outcome}")
        logging.info(f"故障表示:{res.longrepr}")
        logging.info(f"异常:{call.excinfo}")
        logging.info(f"用例耗时:{res.duration}")
        logging.info("**************************************")

日志效果展示

成功的用例:

pytest.ini、conftest这两个文件配置完之后

重新来运行一下test_ds-04.py文件(即上面'项目级token多文件关联的代码'):

在当前项目文件下生成了一个logdata文件,记录了本次执行的详细日志、控制台也输出了详细的日志

失败的用例:

然后重新执行,控制台信息较多,我们直接看日志:

总结

  • 14
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值