テストフレームワークインタフェースyamlpy

1、考え方:

yamlpyのYAMLファイルは、ユニットテストフレームワーク+ pytest略語であります

足場は、ツールとして見ることができ、

すぐにプロジェクト内のさまざまなディレクトリとファイルを生成することができ、

のみYAMLに1つ以上のコピーファイルを維持する必要があり、

あなたは多くのコードを記述する必要はありません。

2、インストール:

ピップyamlpyをインストール

訪問

https://pypi.org/project/yamlpy/

3、ファイルの例:

README.mdファイル:

 1 # yamlpy  
 2 接口测试框架  
 3 
 4 
 5 # 一、思路         
 6 1、采用requests+PyMySQL+demjson+loguru+PyYAML+ruamel.yaml+pytest+pytest-html+allure-pytest+pytest-assume+pytest-rerunfailures+pytest-sugar+pytest-timeout  
 7 2、requests是发起HTTP请求的第三方库    
 8 3、PyMySQL是连接MySQL的第三方库   
 9 4、demjson是解析json的第三方库  
10 5、loguru是记录日志的第三方库  
11 6、PyYAML与ruamel.yaml是读写yaml文件的第三方库  
12 7、pytest是单元测试的第三方库  
13 8、pytest-html是生成html测试报告的插件  
14 9、allure-pytest是生成allure测试报告的插件  
15 10、pytest-assume是多重断言的插件  
16 11、pytest-rerunfailures是失败重跑的插件   
17 12、pytest-sugar是显示进度的插件  
18 13、pytest-timeout是设置超时时间的插件  
19 
20 
21 # 二、目录结构    
22 1、case是测试用例包              
23 2、log是日志目录         
24 3、report是测试报告的目录       
25 4、resource是yaml文件的目录      
26 5、setting是工程的配置文件包            
27 6、tool是常用方法的封装包         
28 
29 
30 # 三、yaml文件说明  
31 1、字段(命名和格式不可修改,顺序可以修改)  
32 case_name: 用例名称  
33 mysql: MySQL语句,列表格式,顺序不可修改  
34 mysql[0]  
35 mysql[1]  
36 mysql[2]  
37 第一行为增删改语句,第二行为查语句,第三行为查语句(数据库双重断言)  
38 第一行是发起请求之前的动作,没有返回结果  
39 第二行是发起请求之前的动作,有返回结果,是为了动态传参  
40 第三行是发起请求之后的动作,有返回结果,但是不可用于动态传参,是为了断言实际的响应结果  
41 当不需要增删改查和双重断言时,三行都为空  
42 当只需要增删改时,第一行为增删改语句,第二行为空,第三行为空  
43 当只需要查时,第一行为空,第二行为查语句,第三行为空  
44 当只需要双重断言时,第一行为空,第二行为空,第三行为查语句  
45 request_mode: 请求方式  
46 api: 接口路径  
47 data: 请求体,缩进字典格式或者json格式     
48 headers: 请求头,缩进字典格式或者json格式    
49 query_string: 请求参数,缩进字典格式或者json格式    
50 expected_code: 预期的响应代码    
51 expected_result: 预期的响应结果,缩进字典格式或者json格式  
52 regular: 正则,缩进字典格式  
53 >>variable:变量名,-列表格式  
54 >>expression:表达式,-列表格式  
55 
56 2、参数化  
57 正则表达式提取的结果用${变量名}匹配,一条用例里面可以有多个    
58 MySQL查询语句返回的结果,即第二行mysql[1]  
59 用{__SQL0}、{__SQL1}、{__SQL2}、{__SQL3}。。。。。。匹配,一条用例里面可以有多个   
60 随机数字用{__RN位数},一条用例里面可以有多个   
61 随机英文字母用{__RL位数},一条用例里面可以有多个  
62 以上4种类型在一条用例里面可以混合使用  
63 ${变量名}的作用域是全局的,其它3种的作用域仅限该条用例  
64 
65 
66 # 四、运行  
67 在工程的根目录下执行命令  
68 pytest+--cmd=环境缩写  
69 pytest --cmd=dev  
70 pytest --cmd=test  
71 pytest --cmd=pre  
72 pytest --cmd=formal  

demo_test.py文件:

"""
测试用例
"""

import json
import re
from itertools import chain
from time import sleep

import allure
import demjson
import pytest
import requests
from pytest_assume.plugin import assume
from setting.project_config import *
from tool.connect_mysql import ConnectMySQL
from tool.read_write_yaml import read_yaml
from tool.function_assistant import function_dollar, function_rn, function_rl, function_sql


@allure.feature(test_scenario)
class DemoTest(object):
    demo_one_list = read_yaml("/demo_one.yaml")
    # 调用读取yaml文件的方法
    demo_two_list = read_yaml("/demo_two.yaml")
    demo_three_list = read_yaml("/demo_three.yaml")

    temporary_list = demo_one_list + \
                     demo_two_list + \
                     demo_three_list

    # 把几个列表合并成一个临时列表

    def setup_class(self):
        self.variable_result_dict = {}
        # 定义一个变量名与提取的结果字典

    @pytest.mark.usefixtures("cmd")
    @allure.story(test_story)
    @allure.severity(test_case_priority[0])
    @allure.testcase(test_case_address, test_case_address_title)
    @pytest.mark.parametrize("temporary_dict", temporary_list)
    # 传入临时列表
    def test_demo(self, temporary_dict):
        """
        测试用例
        :param temporary_dict:
        :return:
        """

        global mysql_result_list_after

        temporary_dict = str(temporary_dict)
        if "None" in temporary_dict:
            temporary_dict = temporary_dict.replace("None", "''")
        temporary_dict = demjson.decode(temporary_dict)
        # 把值为None的替换成''空字符串,因为None无法拼接
        # demjson.decode()等价于json.loads()反序列化

        case_name = temporary_dict.get("case_name")
        # 用例名称
        mysql = temporary_dict.get("mysql")
        # mysql语句
        request_mode = temporary_dict.get("request_mode")
        # 请求方式
        api = temporary_dict.get("api")
        # 接口路径
        if type(api) != str:
            api = str(api)
        payload = temporary_dict.get("data")
        # 请求体
        if type(payload) != str:
            payload = str(payload)
        headers = temporary_dict.get("headers")
        # 请求头
        if type(headers) != str:
            headers = str(headers)
        query_string = temporary_dict.get("query_string")
        # 请求参数
        if type(query_string) != str:
            query_string = str(query_string)
        expected_code = temporary_dict.get("expected_code")
        # 预期的响应代码
        expected_result = temporary_dict.get("expected_result")
        # 预期的响应结果
        if type(expected_result) != str:
            expected_result = str(expected_result)
        regular = temporary_dict.get("regular")
        # 正则

        logger.info("{}>>>开始执行", case_name)
        if environment == "formal" and mysql:
            pass
        # 生产环境不能连接MySQL数据库,因此跳过,此行后面的都不会执行

        if self.variable_result_dict:
            # 如果变量名与提取的结果字典不为空
            if mysql:
                if mysql[0]:
                    mysql[0] = function_dollar(mysql[0], self.variable_result_dict.items())
                # 调用替换$的方法
                if mysql[1]:
                    mysql[1] = function_dollar(mysql[1], self.variable_result_dict.items())
                if mysql[2]:
                    mysql[2] = function_dollar(mysql[1], self.variable_result_dict.items())
            if api:
                api = function_dollar(api, self.variable_result_dict.items())
            if payload:
                payload = function_dollar(payload, self.variable_result_dict.items())
            if headers:
                headers = function_dollar(headers, self.variable_result_dict.items())
            if query_string:
                query_string = function_dollar(query_string, self.variable_result_dict.items())
            if expected_result:
                expected_result = function_dollar(expected_result, self.variable_result_dict.items())
        else:
            pass

        if mysql:
            db = ConnectMySQL()
            # 实例化一个MySQL操作对象
            if mysql[0]:
                mysql[0] = function_rn(mysql[0])
                # 调用替换RN随机数字的方法
                mysql[0] = function_rl(mysql[0])
                # 调用替换RL随机字母的方法
                if "INSERT" in mysql[0]:
                    db.insert_mysql(mysql[0])
                    # 调用插入mysql的方法
                    sleep(2)
                    # 等待2秒钟
                if "UPDATE" in mysql[0]:
                    db.update_mysql(mysql[0])
                    # 调用更新mysql的方法
                    sleep(2)
                if "DELETE" in mysql[0]:
                    db.delete_mysql(mysql[0])
                    # 调用删除mysql的方法
                    sleep(2)
            if mysql[1]:
                mysql[1] = function_rn(mysql[1])
                # 调用替换RN随机数字的方法
                mysql[1] = function_rl(mysql[1])
                # 调用替换RL随机字母的方法
                if "SELECT" in mysql[1]:
                    mysql_result_tuple = db.query_mysql(mysql[1])
                    # mysql查询结果元祖
                    mysql_result_list = list(chain.from_iterable(mysql_result_tuple))
                    # 把二维元祖转换为一维列表
                    logger.info("发起请求之前mysql查询的结果列表为:{}", mysql_result_list)
                    if api:
                        api = function_sql(api, mysql_result_list)
                        # 调用替换MySQL查询结果的方法
                    if payload:
                        payload = function_sql(payload, mysql_result_list)
                    if headers:
                        headers = function_sql(headers, mysql_result_list)
                    if query_string:
                        query_string = function_sql(query_string, mysql_result_list)
                    if expected_result:
                        expected_result = function_sql(expected_result, mysql_result_list)

        if api:
            api = function_rn(api)
            api = function_rl(api)
        if payload:
            payload = function_rn(payload)
            payload = function_rl(payload)
            payload = demjson.decode(payload)
        if headers:
            headers = function_rn(headers)
            headers = function_rl(headers)
            headers = demjson.decode(headers)
        if query_string:
            query_string = function_rn(query_string)
            query_string = function_rl(query_string)
            query_string = demjson.decode(query_string)

        url = service_domain + api
        # 拼接完整地址

        logger.info("请求方式为:{}", request_mode)
        logger.info("地址为:{}", url)
        logger.info("请求体为:{}", payload)
        logger.info("请求头为:{}", headers)
        logger.info("请求参数为:{}", query_string)
        logger.info("预期的响应代码为:{}", expected_code)
        logger.info("预期的响应结果为:{}", expected_result)

        response = requests.request(
            request_mode, url, data=json.dumps(payload),
            headers=headers, params=query_string, timeout=(12, 18)
        )
        # 发起HTTP请求
        # json.dumps()序列化把字典转换成字符串,json.loads()反序列化把字符串转换成字典
        # data请求体为字符串,headers请求头与params请求参数为字典

        actual_time = response.elapsed.total_seconds()
        # 实际的响应时间
        actual_code = response.status_code
        # 实际的响应代码
        actual_result_text = response.text
        # 实际的响应结果(文本格式)

        if mysql:
            if mysql[2]:
                mysql[2] = function_rn(mysql[2])
                mysql[2] = function_rl(mysql[2])
                if "SELECT" in mysql[2]:
                    db_after = ConnectMySQL()
                    mysql_result_tuple_after = db_after.query_mysql(mysql[2])
                    mysql_result_list_after = list(chain.from_iterable(mysql_result_tuple_after))
                    logger.info("发起请求之后mysql查询的结果列表为:{}", mysql_result_list_after)

        logger.info("实际的响应代码为:{}", actual_code)
        logger.info("实际的响应结果为:{}", actual_result_text)
        logger.info("实际的响应时间为:{}", actual_time)

        if regular:
            # 如果正则不为空
            extract_list = []
            # 定义一个提取结果列表
            for i in regular["expression"]:
                regular_result = re.findall(i, actual_result_text)[0]
                # re.findall(正则表达式, 实际的响应结果)返回一个符合规则的list,取第1个
                extract_list.append(regular_result)
                # 把提取结果添加到提取结果列表里面
            temporary_dict = dict(zip(regular["variable"], extract_list))
            # 把变量列表与提取结果列表转为一个临时字典
            for key, value in temporary_dict.items():
                self.variable_result_dict[key] = value
            # 把临时字典合并到变量名与提取的结果字典,已去重
        else:
            pass

        for key in list(self.variable_result_dict.keys()):
            if not self.variable_result_dict[key]:
                del self.variable_result_dict[key]
        # 删除变量名与提取的结果字典中为空的键值对

        expected_result = re.sub("{|}|\'|\"|\\[|\\]| ", "", expected_result)
        actual_result_text = re.sub("{|}|\'|\"|\\[|\\]| ", "", actual_result_text)
        # 去除大括号{、}、单引号'、双引号"、中括号[、]与空格
        expected_result_list = re.split(":|,", expected_result)
        actual_result_list = re.split(":|,", actual_result_text)
        # 把文本转为列表,并去除:与,
        logger.info("切割之后预期的响应结果列表为:{}", expected_result_list)
        logger.info("切割之后实际的响应结果列表为:{}", actual_result_list)

        if expected_code == actual_code:
            # 如果预期的响应代码等于实际的响应代码
            if set(expected_result_list) <= set(actual_result_list):
                # 判断是否是其真子集
                logger.info("{}>>>预期的响应结果与实际的响应结果断言成功", case_name)
            else:
                logger.error("{}>>>预期的响应结果与实际的响应结果断言失败!!!", case_name)
            assume(set(expected_result_list) <= set(actual_result_list))
            # 预期的响应结果与实际的响应结果是被包含关系
            if mysql:
                if mysql[2]:
                    if set(mysql_result_list_after) <= set(actual_result_list):
                        # 判断是否是其真子集
                        logger.info("{}>>>发起请求之后mysql查询结果与实际的响应结果断言成功", case_name)
                    else:
                        logger.error("{}>>>发起请求之后mysql查询结果与实际的响应结果断言失败!!!", case_name)
                    assume(set(mysql_result_list_after) <= set(actual_result_list))
                    # 发起请求之后mysql查询结果与实际的响应结果是被包含关系
            # 双重断言
        else:
            logger.error("{}>>>请求失败!!!", url)
            logger.error("{}>>>执行失败!!!", case_name)
            assume(set(expected_result_list) <= set(actual_result_list))
        logger.info("##########用例分隔符##########\n")


if __name__ == "__main__":
    pytest.main()

project_config.py文件:

"""
整个工程的配置文件
"""

import os
import sys
import time

from loguru import logger

parameter = sys.argv[1]
# 从命令行获取参数
if "--cmd=" in parameter:
    parameter = parameter.replace("--cmd=", "")
else:
    pass

environment = os.getenv("measured_environment", parameter)
# 环境变量

if environment == "dev":
    service_domain = "http://www.dev.com"
    # 开发环境
    db_host = 'mysql.dev.com'
    db_port = 3306
elif environment == "test":
    service_domain = "http://www.test.com"
    # 测试环境
    db_host = 'mysql.test.com'
    db_port = 3307
elif environment == "pre":
    service_domain = "http://www.pre.com"
    # 预生产环境
    db_host = 'mysql.pre.com'
    db_port = 3308
elif environment == "formal":
    service_domain = "https://www.formal.com"
    # 生产环境
    db_host = None
    db_port = None

db_user = 'root'
db_password = '123456'
db_database = ''
# MySQL数据库配置


current_path = os.path.dirname(os.path.dirname(__file__))
# 获取当前目录的父目录的绝对路径
# 也就是整个工程的根目录
case_path = os.path.join(current_path, "case")
# 测试用例的目录
yaml_path = os.path.join(current_path, "resource")
# yaml文件的目录
today = time.strftime("%Y-%m-%d", time.localtime())
# 年月日

report_path = os.path.join(current_path, "report")
# 测试报告的目录
if os.path.exists(report_path):
    pass
else:
    os.mkdir(report_path, mode=0o777)

log_path = os.path.join(current_path, "log")
# 日志的目录
if os.path.exists(log_path):
    pass
else:
    os.mkdir(log_path, mode=0o777)

logging_file = os.path.join(log_path, "log{}.log".format(today))

logger.add(
    logging_file,
    format="{time:YYYY-MM-DD HH:mm:ss}|{level}|{message}",
    level="INFO",
    rotation="500 MB",
    encoding="utf-8",
)
# loguru日志配置


test_scenario = "测试场景:XXX接口测试"
test_story = "测试故事:XXX接口测试"
test_case_priority = ["blocker", "critical", "normal", "minor", "trivial"]
test_case_address = "http://www.case.com"
test_case_address_title = "XXX接口测试用例地址"
# allure配置

 

おすすめ

転載: www.cnblogs.com/yjlch1016/p/12348199.html