Python接口测试实战4(下) - 框架完善:用例基类,用例标签,重新运行上次失败用例

news2024/11/15 13:44:45

本节内容

  • 使用用例基类

  • 自定义TestSuite

  • collect-only的实现

  • testlist的实现

  • 用例tags的实现

  • rerun-fails的实现

  • 命令行参数的使用

更简单的用例编写

使用用例基类

因为每条用例都需要从excel中读取数据,解析数据,发送请求,断言响应结果,我们可以封装一个BaseCase的用例基础类,对一些方法进行封装,来简化用例编写

重新规划了test目录,在test下建立case文件夹存放用例,建立suite文件夹存放自定义的TestSuite
test_user_data.xlsx中增加了一列data_type,FORM指表单格式请求,JSON指JSON格式请求

项目test/case文件夹下新建basecase.py

Copyimport unittest
import requests
import json
import sys
sys.path.append("../..")   # 统一将包的搜索路径提升到项目根目录下

from lib.read_excel import *  
from lib.case_log import log_case_info 

class BaseCase(unittest.TestCase):   # 继承unittest.TestCase
    @classmethod
    def setUpClass(cls):
        if cls.__name__ != 'BaseCase':
            cls.data_list = excel_to_list(data_file, cls.__name__)

    def get_case_data(self, case_name):
        return get_test_data(self.data_list, case_name)

    def send_request(self, case_data):
        case_name = case_data.get('case_name')
        url = case_data.get('url')
        args = case_data.get('args')
        headers = case_data.get('headers')
        expect_res = case_data.get('expect_res')
        method = case_data.get('method')
        data_type = case_data.get('data_type')

        if method.upper() == 'GET':   # GET类型请求
            res = requests.get(url=url, params=json.loads(args))

        elif data_type.upper() == 'FORM':   # 表单格式请求
            res = requests.post(url=url, data=json.loads(args), headers=json.loads(headers))
            log_case_info(case_name, url, args, expect_res, res.text)
            self.assertEqual(res.text, expect_res)
        else:
            res = requests.post(url=url, json=json.loads(args), headers=json.loads(headers))   # JSON格式请求
            log_case_info(case_name, url, args, json.dumps(json.loads(expect_res), sort_keys=True),
                          json.dumps(res.json(), ensure_ascii=False, sort_keys=True))
            self.assertDictEqual(res.json(), json.loads(expect_res))

简化后的用例:

test/case/user/test_user_login.py

Copyfrom test.case.basecase import BaseCase


classTestUserLogin(BaseCase):   # 这里直接继承BaseCasedeftest_user_login_normal(self):
        """level1:正常登录"""
        case_data = self.get_case_data("test_user_login_normal")
        self.send_request(case_data)

    deftest_user_login_password_wrong(self):
        """密码错误登录"""
        case_data = self.get_case_data("test_user_login_password_wrong")
        self.send_request(case_data)

test/case/user/test_user_reg.py

Copyfrom test.case.basecase import BaseCase
from lib.db import *
import json


classTestUserReg(BaseCase):

    deftest_user_reg_normal(self):
        case_data = self.get_case_data("test_user_reg_normal")

        # 环境检查
        name = json.loads(case_data.get("args")).get('name')  # 范冰冰if check_user(name):
            del_user(name)
        # 发送请求
        self.send_request(case_data)
        # 数据库断言
        self.assertTrue(check_user(name))
        # 环境清理
        del_user(name)

    deftest_user_reg_exist(self):
        case_data = self.get_case_data("test_user_reg_exist")

        name = json.loads(case_data.get("args")).get('name')
        # 环境检查ifnot check_user(name):
            add_user(name, '123456')

        # 发送请求
        self.send_request(case_data)

更灵活的运行方式

之前我们的run_all.py只有运行所有用例一种选择,我们通过增加一些功能,提供更灵活的运行策略

运行自定义TestSuite

项目test/suite文件夹下新建test_suites.py

Copyimport unittest
import sys
sys.path.append("../..")
from test.case.user.test_user_login import TestUserLogin
from test.case.user.test_user_reg import TestUserReg

smoke_suite = unittest.TestSuite()  # 自定义的TestSuite
smoke_suite.addTests([TestUserLogin('test_user_login_normal'), TestUserReg('test_user_reg_normal')])

defget_suite(suite_name):    # 获取TestSuite方法returnglobals().get(suite_name)

修改run_all.py为run.py,添加run_suite()方法

Copyimport unittest
from lib.HTMLTestReportCN import HTMLTestRunner
from config.config import *
from lib.send_email import send_email
from test.suite.test_suites import *

defdiscover():
    return unittest.defaultTestLoader.discover(test_case_path)

defrun(suite):
    logging.info("================================== 测试开始 ==================================")
    withopen(report_file, 'wb') as f: 
         HTMLTestRunner(stream=f, title="Api Test", description="测试描述", tester="卡卡").run(suite)
   
    # send_email(report_file)  
    logging.info("================================== 测试结束 ==================================")

defrun_all():  # 运行所用用例
    run(discover())

defrun_suite(suite_name):  # 运行`test/suite/test_suites.py`文件中自定义的TestSuite
    suite = get_suite(suite_name)
    if suite:
        run(suite)
    else:
        print("TestSuite不存在")

只列出所有用例(并不执行)

run.py中添加

Copydef collect():   # 由于使用discover() 组装的TestSuite是按文件夹目录多级嵌套的,我们把所有用例取出,放到一个无嵌套的TestSuite中,方便之后操作
    suite = unittest.TestSuite()

    def _collect(tests):   # 递归,如果下级元素还是TestSuite则继续往下找
        if isinstance(tests, unittest.TestSuite):
            if tests.countTestCases() != 0:
                for i in tests:
                    _collect(i)
        else:
            suite.addTest(tests)  # 如果下级元素是TestCase,则添加到TestSuite中

    _collect(discover())
    return suite

def collect_only():   # 仅列出所用用例
    t0 = time.time()
    i = 0
    for case in collect():
        i += 1print("{}.{}".format(str(i), case.id()))
    print("----------------------------------------------------------------------")
    print("Collect {} tests is {:.3f}s".format(str(i),time.time()-t0))

按testlist用例列表运行

test文件夹下新建testlist.txt,内容如下

Copytest_user_login_normal
test_user_reg_normal
# test_user_reg_exist   # 注释后不执行

run.py中添加

Copydef makesuite_by_testlist(testlist_file):  # test_list_file配置在config/config.py中
    withopen(testlist_file) as f:
        testlist = f.readlines()

    testlist = [i.strip() for i in testlist ifnot i.startswith("#")]   # 去掉每行结尾的"/n"和 #号开头的行

    suite = unittest.TestSuite() 
    all_cases = collect()  # 所有用例forcasein all_cases:  # 从所有用例中匹配用例方法名ifcase._testMethodName in testlist:
            suite.addTest(case)
    return suite

按用例标签运行

由于TestSuite我们必须提前组装好,而为每个用例方法添加上标签,然后运行指定标签的用例能更加灵活

遗憾的是,unittest并没有tag相关功能,一种实现方案是:

Copydeftag(tag):
    if tag==OptionParser.options.tag:   # 运行的命令行参数returnlambda func: func    # 如果用例的tag==命令行指定的tag参数,返回用例本身return unittest.skip("跳过不包含该tag的用例")    #  否则跳过用例

用例标记方法

Copy@tag("level1")deftest_a(self):
    pass

这种方法在最后的报告中会出现很多skipped的用例,可能会干扰到因其他(如环境)原因需要跳过的用例

我这里的实现方法是通过判断用例方法中的docstring中加入特定的标签来重新组织TestSuite的方式

run.py中添加

Copydefmakesuite_by_tag(tag):
    suite = unittest.TestSuite()
    forcasein collect():
        ifcase._testMethodDoc and tag incase._testMethodDoc:# 如果用例方法存在docstring,并且docstring中包含本标签
            suite.addTest(case)
    return suite

用例标记方法

CopyclassTestUserLogin(BaseCase):
    deftest_user_login_normal(self):
        """level1:正常登录"""# level1及是一个标签,放到docstring哪里都可以
        case_data = self.get_case_data("test_user_login_normal")
        self.send_request(case_data)

重新运行上次失败用例

我们在每次执行后,通过执行结果result.failures获取到失败的用例,组装成TestSuite并序列化到指定文件中,rerun-fails时,反序列化得到上次执行失败的TestSuite, 然后运行

在run.py中添加

Copyimport pickle
import sys

defsave_failures(result, file):   # file为序列化保存的文件名,配置在config/config.py中
    suite = unittest.TestSuite()
    for case_result in result.failures:   # 组装TestSuite
        suite.addTest(case_result[0])   # case_result是个元祖,第一个元素是用例对象,后面是失败原因等等withopen(file, 'wb') as f:
        pickle.dump(suite, f)    # 序列化到指定文件defrerun_fails():  # 失败用例重跑方法
    sys.path.append(test_case_path)   # 需要将用例路径添加到包搜索路径中,不然反序列化TestSuite会找不到用例withopen(last_fails_file, 'rb') as f:
        suite = pickle.load(f)    # 反序列化得到TestSuite
    run(suite)

修改run.py中的run()方法,运行后保存失败用例序列化文件

Copydefrun(suite):
    logging.info("================================== 测试开始 ==================================")

    withopen(report_file, 'wb') as f: 
        # 结果赋予result变量
        result = HTMLTestRunner(stream=f, title="Api Test", description="测试描述", tester="卡卡").run(suite)  

    if result.failures:   # 保存失败用例序列化文件
        save_failures(result, last_fails_file)

    # send_email(report_file)  # 从配置文件中读取
    logging.info("================================== 测试结束 ==================================")

使用命令行参数

命令行参数是我们通过命令行调用run.py(执行入口文件)传递的一些参数,通过不同的参数,执行不同的运行策略,如python run.py --collect-only

我们通过optparser实现命令行参数:

在config/config.py中添加

Copy# 命令行选项
parser = OptionParser()

parser.add_option('--collect-only', action='store_true', dest='collect_only', help='仅列出所有用例')
parser.add_option('--rerun-fails', action='store_true', dest='rerun_fails', help='运行上次失败的用例')
parser.add_option('--testlist', action='store_true', dest='testlist', help='运行test/testlist.txt列表指定用例')

parser.add_option('--testsuite', action='store', dest='testsuite', help='运行指定的TestSuite')
parser.add_option('--tag', action='store', dest='tag', help='运行指定tag的用例')

(options, args) = parser.parse_args()  # 应用选项(使生效)
  • '--conllect-only'是参数名,dest='collect-only'指存储到 options.collect_only变量中,'store_true'指,如果有该参数,options.collect_only=True

  • 'store'指将--testsuite='smoke_suite',参数的值'smoke_suite'存到options.testsuite变量中

命令行选项使用方法:

run.py中添加:

Copyfrom config.config import *

defmain():
    if options.collect_only:    # 如果指定了--collect-only参数
        collect_only()
    elif options.rerun_fails:    # 如果指定了--rerun-fails参数
        rerun_fails()
    elif options.testlist:    # 如果指定了--testlist参数
        run(makesuite_by_testlist(testlist_file))
    elif options.testsuite:  # 如果指定了--testsuite=***
        run_suite(options.testsuite)
    elif options.tag:  # 如果指定了--tag=***
        run(makesuite_by_tag(options.tag))
    else:   # 否则,运行所有用例
        run_all()

if __name__ == '__main__':
    main()   # 调用main()

运行结果:

CopyC:\Users\hanzhichao\PycharmProjects\api_test_framework_finish>python run.py --collect-only1.user.test_user_login.TestUserLogin.test_user_login_normal
2.user.test_user_login.TestUserLogin.test_user_login_password_wrong
3.user.test_user_reg.TestUserReg.test_user_reg_exist
4.user.test_user_reg.TestUserReg.test_user_reg_normal
----------------------------------------------------------------------Collect4 tests is0.006s
C:\Users\hanzhichao\PycharmProjects\api_test_framework_finish>python run.py --rerun-fails
.
Time Elapsed: 0:00:00.081812
C:\Users\hanzhichao\PycharmProjects\api_test_framework_finish>python run.py --testlist
..
Time Elapsed: 0:00:00.454654
C:\Users\hanzhichao\PycharmProjects\api_test_framework_finish>python run.py --testsuite=smoke_suite
..
Time Elapsed: 0:00:00.471255
C:\Users\hanzhichao\PycharmProjects\api_test_framework_finish>python run.py --tag=level1
.
Time Elapsed: 0:00:00.062273
C:\Users\hanzhichao\PycharmProjects\api_test_framework_finish>python run.py 
....
Time Elapsed: 0:00:00.663564

其他优化

1.按天生成log,每次执行生成新的报告

修改config/config.py

Copyimport time

today = time.strftime('%Y%m%d', time.localtime())
now = time.strftime('%Y%m%d_%H%M%S', time.localtime())

log_file = os.path.join(prj_path, 'log', 'log_{}.txt'.format(today))  # 更改路径到log目录下
report_file = os.path.join(prj_path, 'report', 'report_{}.html'.format(now))  # 更改路径到report目录下

2.增加send_email()开关

config/config.py增加

Copysend_email_after_run = False

修改run.py

Copyfrom config.config import *

defrun(suite):
    logging.info("================================== 测试开始 ==================================")

    withopen(report_file, 'wb') as f:  # 从配置文件中读取
        result = HTMLTestRunner(stream=f, title="Api Test", description="测试描述", tester="卡卡").run(suite)

    if result.failures:
        save_failures(result, last_fails_file)

    if send_email_after_run:  # 是否发送邮件
        send_email(report_file)  
    logging.info("================================== 测试结束 ==================================")
高效学习,快速掌握Python自动化所有领域技能
同步快速解决各种问题
配套实战项目练习

如有不懂还要咨询下方小卡片,博主也希望和志同道合的测试人员一起学习进步

在适当的年龄,选择适当的岗位,尽量去发挥好自己的优势。

我的自动化测试开发之路,一路走来都离不每个阶段的计划,因为自己喜欢规划和总结,

测试开发视频教程、学习笔记领取传送门!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/187523.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

阿里云ECS学习笔记1

ECS:弹性计算服务。CPU可以热插、内存可变大变小、硬盘可以增加~ 注册: 在企业中,应该以企业的身份进行注册,而不是以个人身份进行注册。 手机号:行政部门专门管理注册的账号资源的,而不使用个人或者老板…

【可解释性机器学习】解释基于XGBoost对泰坦尼克号数据集的预测过程和结果

解释基于XGBoost对泰坦尼克号数据集的预测过程和结果1. 训练数据2. 简单的 XGBoost 分类器3. 解释重量4. 解释预测5. 添加文本特性参考资料本文介绍如何分析XGBoost分类器的预测( eli5也支持 XGBoost和大多数 scikit-learn树集成的回归)。 我们将使用 Ti…

【数据结构】8.5 归并排序

文章目录相邻两个有序子序列的归并归并排序算法归并排序算法分析基本思想 将两个或两个以上的有序子序列归并为一个有序序列。在内部排序中,通常采用的是2-路归并排序。 即:将两个位置相邻的有序子序列 R[l…m] 和 R[m1…n] 归并为一个有序序列 R[l…n]…

1个寒假能学会多少网络安全技能?

现在可以看到很多标题都声称三个月内就可以转行网络安全领域,并且成为月入15K的网络工程师。那么,这个寒假的时间能学多少网络安全知识?是否能入门网络安全工程师呢? 答案是肯定的。 虽然网络完全知识是一门广泛的学科&#xff…

在线支付系列【9】微信支付之申请微信公众号

有道无术,术尚可求,有术无道,止于术。 文章目录前言申请微信公众号前言 由于微信支付的产品体系全部搭载于微信的社交体系之上,所以直连商户或服务商接入微信支付之前,都需要有一个微信社交载体,该载体对应…

天啦撸~ChatGPT通过国际软件测试工程师(ISTQB)认证了~

天啦撸!目前最火的AI应用ChatGPT通过ISTQB认证了~ 近期,国外的一位工程师,放出了他用ChatGPT通过认证的相关信息。 ChatGPT相信大家都知道是什么了,ISTQB相信很多测试小伙伴也不陌生,而且很多考证的小伙伴也对此梦寐以…

Linux之网络性能测试工具netperf实践

一、netperf简介 Netperf是一种网络性能的测量工具,主要针对基于TCP或UDP的传输。Netperf根据应用的不同,可以进行不同模式的网络性能测试,即批量数据传输(bulk data transfer)模式和请求/应答(request/rep…

公司通知要大裁员,hr太强势,和所有人吵起来,老板见势不妙,不得不答应大家要求,把HR一起裁掉了!...

在裁员中,hr一般都会代表老板的利益和员工对抗,但如果hr和员工闹翻了,老板会维护hr吗?一位网友说:一上班就收到消息要裁员,立马让报上名单面谈,锁电脑关权限。后面那些人面谈的时候吵起来了&…

OpenAI Chatgpt注册及使用教程

零、什么是chatgpt?​ 1、简介 ChatGPT(Chat Generative Pre-trained Transformer)是OpenAI于 2022 年 11 月推出的聊天机器人。它建立在 OpenAI 的GPT-3大型语言模型家族之上,并经过微调(一种迁移学习的方法)…

双点双向的ISIS与OSPF、OSPF与OSPF、ISIS与ISIS环境以路由策略解决(1tag、2tag、4tag介绍与配置)

3.1.1 双点双向的ISIS与OSPF、OSPF与OSPF、ISIS与ISIS环境以路由策略解决(1tag、2tag、4tag介绍与配置) OSPF与ISIS双点双向 次优的产生与解决: 由于OSPF引入外部路由之后其优先级为150,再由ASBR进行双向引入之后。 原先OSPF外部…

闲鱼自动化软件——筛选/发送系统 V20已经测试完毕

做程序,就是不断地改,不断地优化。当改动达到一定程序,已经和前面形成断代,程序的升级时刻便到了。V20做了哪些更改或优化。1。优化抓取:在抓取环境优化参数,使抓取更顺滑,抓取数据效果上更准确…

智能家居创意DIY-Homekit智能灯

一、什么是智能灯 传统的灯泡是通过手动打开和关闭开关来工作。有时,它们可以通过声控、触控、红外等方式进行控制,或者带有调光开关,让用户调暗或调亮灯光。 智能灯泡内置有芯片和通信模块,可与手机、家庭智能助手、或其他智能…

若依-excel预览功能实现

实现效果及源码 实现效果如下图所示: 实现思路: 1.动态表格:定义表头数组,表格遍历表头生成表格列 2.读取excel文件内容,封装表头,绑定表格数据 代码修改 首先参考若依官网,先实现excel导入功…

C++基础——C++ 字符串

C基础——C 字符串C 字符串C 风格字符串C 中的 String 类C 字符串 C 提供了以下两种类型的字符串表示形式: C 风格字符串C 引入的 string 类类型 C 风格字符串 C 风格的字符串起源于 C 语言,并在 C 中继续得到支持。字符串实际上是使用 null 字符 ‘…

126、【回溯算法】leetcode ——332. 重新安排行程:回溯算法(C++版本)

题目描述 原题链接:332. 重新安排行程 解题思路 本题要解决的问题: 需要构建起始与目的机场的映射关系;每次选择目的机场时,需要选择当前最小字母顺序的机场;从“JFK”之后依次飞往,并且可能会有多条路径…

58同城AI Lab在WeNet中开源Efficient Conformer模型

2022年8月,58同城TEG-AI Lab语音技术团队完成了WeNet端到端语音识别的大规模落地,替换了此前基于Kaldi的系统,并针对业务需求对识别效果和推理速度展开优化,取得了优异的效果,当前录音文件识别引擎处理语音时长达1000万…

非标设备ERP管理系统可以帮助企业解决哪些管理难题?

多品种、小批量、交货周期短、非标准化生产是大多数非标设备制造企业共同的特性,这就要求非标设备制造企业应具备足够的经营、技术、生产和管理力量,否则就会顾此失彼,产品质量难以得到保证。非标设备制造企业常见的管理难题(1&am…

DynaSLAM-2 DynaSLAM中Mask R-CNN部分源码解析(Ⅰ)

目录 1.Mask R-CNN源码地址 2.Mask R-CNN效果 3.项目配置 4.源码使用 1.Mask R-CNN源码地址 Mask R-CNN源码地址https://github.com/matterport/Mask_RCNN/releases 这里我们拿Mask R-CNN2.1版本进行讲解。 2.Mask R-CNN效果 最传统最核心的功能就是物体检测了…

4款让人心疼的电脑软件,由于免费又实用,常被同行挤压

许多小众软件,免费、实用、体验好、无广告,出淤泥而不染,却因过于良心备受排挤,让人唏嘘。 1、oCam 市面上的视频录屏工具,要么限制时长,要么附上水印,需要使用完整功能必须付费,oca…

Java项目调用C++端的订阅功能,获得推送数据(从设计到代码全栈完整过程)

前言 有关java和C的交互的基本概念和知识,本文不再详述。有需要的可以参考我的这篇文章。 JNI、DLL、SO等相关概念 开发背景 C项目端开发了一套股票市场资讯推送的功能,多个小组都会用到该功能,为了避免重复开发,中台小组要负担…