python 自动化福音,30行代码手撸ddt模块

news2024/9/18 12:07:18

用 python 做过自动化的小伙伴,大多数都应该使用过 ddt 这个模块,不可否认 ddt 这个模块确实挺好用,可以自动根据用例数据,来生成测试用例,能够很方便的将测试数据和测试用例执行的逻辑进行分离。

接下来就带大家一起手把手撸出一个 ddt:

1、DDT 的实现原理

首先我们来看一下 ddt 的基本使用:

图片

ddt 在使用时非常简洁,也就是两个装饰器,@ddt 这个装饰器装饰测试类,@data 这个装饰器装饰器用例方法并传入测试数据。这两个装饰器实现的效果就是根据传入的用例数据自动生成用例。

如果你想学习自动化测试,我这边给你推荐一套视频,这个视频可以说是B站播放全网第一的自动化测试教程,同时在线人数到达1000人,并且还有笔记可以领取及各路大神技术交流:798478386   

【已更新】B站讲的最详细的Python接口自动化测试实战教程全集(实战最新版)_哔哩哔哩_bilibili【已更新】B站讲的最详细的Python接口自动化测试实战教程全集(实战最新版)共计200条视频,包括:1、接口自动化之为什么要做接口自动化、2、接口自动化之request全局观、3、接口自动化之接口实战等,UP主更多精彩视频,请关注UP账号。icon-default.png?t=N7T8https://www.bilibili.com/video/BV17p4y1B77x/?spm_id_from=333.337.search-card.all.click

具体是怎么实现的呢?其实实现的思路也特别的简单,也就两个步骤:

第一步:把传进来的用例数据保存起来

第二步:遍历用例数据,每遍历一条数据 就动态的给测试类添加一个用例方法。

ddt 中的两个装饰器其实实现的就是这么两个步骤:

@data:做的是第一步将传入测试数据保存起来;

@ddt 做的是第二步,遍历用例数据,给测试类动态添加用例方法。

2、data 装饰器的实现

前面我们说到 data 这个装饰器,做的事情是将用例数据保存起来。

那么如何保存呢?其实最简单的方式就是保存被装饰的这个用例方法的属性。

接下来我们来具体实现:

先看一个 ddt 使用的案例

@ddt
class TestLogin(unittest.TestCase):

    @data(11,22)
    def test_login(self, item):
      pass

了解过装饰器装饰器原理的小伙伴,应该都知道上面@data(11,22) 这行代码执行的效果等同于

test_login = data(11,22)(test_login)

接下来我们来分析一下上面这行代码,首先是调用 data 这个装饰器函数,把用例数据 11,22 当成参数传入进去,然后返回一个可调用对象(函数),再次调用返回的函数并把用例方法传入进去。明确了调用的流程,那么我们就可以结合之前的需求去定义 data 这个装饰器函数了。

具体实现如下:

def data(*args):
    def wrapper(func):
        setattr(func, "PARAMS", args)
        return func
    return wrapper

代码解读:

前面的案例在使用 data 时,执行的 test_login = data(11,22)(test_login)
先调用 data 传入的 11,22 通过不定长参数 args 接收,然后返回嵌套的函数 wrapper
然后调用返回的 wrapper 函数,传入被装饰的 test_login 方法
在 wrapper 函数中我们把用例数据保存为 test_login 这个方法的 PARAMS 属性,再把 test_login 返回
到此为止,data 这个装饰器我们就实现用例数据的保存

3、ddt 装饰器的实现

通过 data 这个装饰器我们实现了用例数据保存之后,我们接下来实现 ddt 这个装饰器,根据用例数据生成测试用例。前面的案例 @ddt 装饰测试类的时候,实际上执行的效果等同于下面的代码

TestLogin = ddt(TestLogin)

这行代码就是把被装饰器的类传入到 ddt 这个装饰器函数中,再把返回值赋值给 TestLogin。之前我们分析的时候说了 ddt 这个装饰器做的事情是遍历用例数据,动态的给测试类添加用例方法。

接下来我们就来实现 ddt 这个装饰器内部的逻辑。

def ddt(cls):
    for name, func in list(cls.__dict__.items()):
        if hasattr(func, "PARAMS"):
            for index, case_data in enumerate(getattr(func, "PARAMS")):
                new_test_name ="{}_{}".format(name,index)
                setattr(cls, new_test_name, func)
            else:
                delattr(cls, name)
    return cls

代码解读:

ddt 函数内部逻辑说明:
1、调用 ddt 这个函数时会把测试类当成参数传入进来,
2、然后通过 cls.__dict__ 获取测试的所有属性和方法,进行遍历
3、判断变量出来的属性或方法 有没有 PARAMS 这个属性,
4、如果有,则说明这个方法用 data 装饰器装饰过并传入了用例数据。
5、通过 getattr(func, "PARAMS")获取所有的用例数据,进行遍历。
6、每遍历出来一组用例数据,生产一个用例方法名, 再动态的给测试类添加一个用例方法。
7、遍历完所有用例数据之后,删除测试类原来定义的测试方法
8、最后返回测试类

当目前为止 ddt 和 data 这两个装饰器函数的基本功能实现了,可以自动根据用例数据生成测试用例了,接下来我们写个测试类来检查一下

# 定义装饰器函数data
def data(*args):
    def wrapper(func):
        setattr(func, "PARAMS", args)
        return func

    return wrapper

# 定义装饰器函数ddt
def ddt(cls):
    for name, func in list(cls.__dict__.items()):
        if hasattr(func, "PARAMS"):
            for index, case_data in enumerate(getattr(func, "PARAMS")):
                new_test_name = "{}_{}".format(name, index)
                setattr(cls, new_test_name, func)
            else:
                delattr(cls, name)
    return cls


import unittest

# 编写测试类
@ddt
class TestDome(unittest.TestCase):
    @data(11, 22, 33, 44)
    def test_demo(self):
        pass

运行上述用例,我们就会发现执行了四条用例,根据用例数据生成用例的功能就已经实现了

4、解决用例参数传递的问题

虽然上面基本的功能已经实现了,但是还存在一个问题:用例的数据没有传递到用例方法中。那么用例数据传递怎么实现了,我们可以通过一个闭包函数对用例方法进行修,从而实现在调用用例方法的时候,把用例测试当成参数传递进去。

修改原有用例方法的函数代码如下

from functools import wraps

def update_test_func(test_func,case_data):
    @wraps(test_func)
    def wrapper(self):
        return test_func(self, case_data)
    return wrapper

代码解读:

上面我们定义了一个叫做 update_test_func 的闭包函数
闭包函数接收两个参数:test_func(接收用例方法),case_data(接收用例数据)
闭包函数返回一个嵌套函数,嵌套函数内部调用原来的用例方法,并传入测试数据
嵌套函数在定义时,使用了 functools 模块中的装饰器 wraps 来装饰,它可以让 wrapper 这个嵌套函数具有 test_func 这个用例函数的相关属性。

下面我们回到前面写的 ddt 这个函数中,在给测试类添加用例之前,调用 update_test_func 方法对用例方法进行修改:

def ddt(cls):
    for name, func in list(cls.__dict__.items()):
        if hasattr(func, "PARAMS"):
            for index, case_data in enumerate(getattr(func, "PARAMS")):
                # 生成一个用例方法名
                new_test_name = "{}_{}".format(name, index)
                # 修改原有的测试方法,设置用例数据为测试方法的参数
                test_func = update_test_func(func,case_data)
                setattr(cls, new_test_name, test_func)
            else:
                delattr(cls, name)
    return cls

通过加上这一步之后,我们在测试类中 动态给测试类添加的测试方法,其实指向的全部是 update_test_func 里面定义的 wrapper 函数,在执行测试用的时候实际上也是执行的 wrapper 函数,而在 wrapper 函数内部,我们调用了原来定义的测试方法,并将用例数据传入了进去。

到此为止 ddt 的功能我们就完全实现了!

End:

给大家举一个完整的案例,大家可以复制过去运行,也可以自己去写一遍,还可以根据自己的一些需求进行自定义的扩展。

完整案例


from functools import wraps
import unittest

# --------ddt的实现--------
def data(*args):
    def wrapper(func):
        setattr(func, "PARAMS", args)
        return func

    return wrapper


def update_test_func(test_func, case_data):
    @wraps(test_func)
    def wrapper(self):
        return test_func(self, case_data)

    return wrapper


def ddt(cls):
    for name, func in list(cls.__dict__.items()):
        if hasattr(func, "PARAMS"):
            for index, case_data in enumerate(getattr(func, "PARAMS")):
                # 生成一个用例方法名
                new_test_name = "{}_{}".format(name, index)
                # 修改原有的测试方法,设置用例数据为测试方法的参数
                test_func = update_test_func(func, case_data)
                setattr(cls, new_test_name, test_func)
            else:
                delattr(cls, name)
    return cls

# --------测试用例编写--------
@ddt
class TestDome(unittest.TestCase):
    @data(11, 22, 33, 44)
    def test_demo(self, data):
        assert data < 40
#---------用例执行-----------
unittest.main()

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

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

相关文章

微服务:何为RPC框架

前言 最近在看有关分布式和微服务的知识&#xff0c;首先第一个碰到的就是RPC框架&#xff0c;常见的RPC框架其实有很多&#xff0c;比较常见的比如&#xff1a;阿里的Dubbo、ApacheThrift、谷歌的gRPC、腾讯的tRPC等等。RPC作为远程调用协议在微服务架构中可以说是比较常见了&…

基于GATK流程化进行SNP calling

在进行变异检测时&#xff0c;以群体基因组重测序数据为例&#xff0c;涉及到的个体基本都是上百个&#xff0c;而其中大多数流程均是重复的步骤。 本文将基于GATK进行SNP calling的流程写入循环&#xff0c;便于批量分析。 1 涉及变量 1.工作目录work_dir/ 2.参考基因组ref…

每天一点python——day74

#每天一点Python——74 #函数调用的参数传递&#xff1a;位置与关键字函数参数传递指的是函数调用时候的传递 一般有两种&#xff1a; 位置实参传递 关键字实参传递#位置实参传递 #我们昨天定义了一个函数 def jiafa(a,b):#我们定义了两个参数&#xff0c;a和b&#xff0c;他们…

用公式告诉你 现货黄金投资者要不要换策略?

看过笔者相关文章的朋友都知道&#xff0c;其实笔者是相当不鼓励投资者更改策略的。但这并不意味着&#xff0c;策略不能改或者换。之所以反对更改策略&#xff0c;是因为很多人对自己的策略还没上手&#xff0c;没了解清楚就急着换策略&#xff0c;这是没必要的。通过下面这个…

1688商品详情原数据(2023年11月最新版)

返回数据&#xff1a; 请求链接 {"item": {"desc_wdescContent": {"itemProperties": [],"offerId": "705844836943","wdescContent": {"content": "<div id\"offer-template-0\"&g…

【实用干货】如何用LightningChart创建WPF 2D热图?(一)

LightningChart.NET完全由GPU加速&#xff0c;并且性能经过优化&#xff0c;可用于实时显示海量数据-超过10亿个数据点。 LightningChart包括广泛的2D&#xff0c;高级3D&#xff0c;Polar&#xff0c;Smith&#xff0c;3D饼/甜甜圈&#xff0c;地理地图和GIS图表以及适用于科学…

如何看待阿里云发布的全球首个容器计算服务 ACS?

如何看待阿里云发布的全球首个容器计算服务 ACS&#xff1f; 本文目录&#xff1a; 前言 一、什么是ACS 二、ACS 的核心特性 三、ACS 的关键技术 四、本期话题讨论 4.1、你如何看待容器计算服务 ACS 的发布&#xff1f; 4.2、你认为 ACS 的产品设计能降低企业使用 K8s的…

Springboot+vue的机动车号牌管理系统(有报告)。Javaee项目,springboot vue前后端分离项目

演示视频: Springbootvue的机动车号牌管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot vue前后端分离项目 项目介绍&#xff1a; 本文设计了一个基于Springbootvue的前后端分离的机动车号牌管理系统&#xff0c;采用M&#xff08;model&#xff09…

【如何让你的建筑设计更高效】推荐7个3DMAX建筑设计的实用插件

3DMAX是创建具有复杂对象和照片级真实感材质的大型三维项目的绝佳工具。它有用于粒子模拟和参数化建模的内置工具&#xff0c;只要有足够的时间和练习&#xff0c;你就可以创建任何东西。然而&#xff0c;总有改进的余地。许多第三方开发人员已经发布了自己的扩展&#xff0c;也…

ChatGPT API 学习

参考&#xff1a;从零开始的 ChatGPT API 使用指南&#xff0c;只需三步&#xff01; - 知乎 (zhihu.com) ChatGPT API 是一种由 OpenAI 提供的 API&#xff0c;它可以用最简单的方式把 ChatGPT 的聊天能力接入到各种应用程序或服务中。 自然语言语音识别(Natural Language S…

Selenium IDE录制脚本

文章目录 1.环境搭建1.1 Chrome浏览器安装1.2 Chrome驱动安装1.3 Selenium IDE插件的安装 2.Selenium IDE插件介绍2.1 初始化界面2.2 菜单栏2.3 工具栏2.4 地址栏2.5 测试用例窗口2.6 测试脚本窗口2.7 日志和引用窗口 3.元素定位3.1 通过id进行元素定位3.2 通过name进行元素定位…

springboot上传文件后显示权限不足

前言&#xff1a; 最近一个老项目迁移&#xff0c;原本一直好好的&#xff0c;迁移后上传文件的功能使用不正常&#xff0c;显示文件没有可读取权限&#xff0c;这个项目并不是我们开发和配置的&#xff0c;由第三方开发的&#xff0c;我们只是接手一下。 前端通过api上传文件…

Unity中 Start和Awake的区别

Awake和Start在Unity中都是MonoBehaviour脚本中的生命周期函数 Awake函数在游戏对象首次被加载时调用&#xff0c;在游戏对象初始化之前调用。 start函数在游戏对象初始化完成后调用&#xff0c;在update第一次执行前调用。 这两个函数在其生命周期内都只会调用一次&#xf…

el-tree结合el-switch实现状态切换

<template><div><el-col :span"24"><el-card class"tree-card"><div class"sketch_content selectFile"><span class"span_title">组织列表 </span><div style"display: flex; jus…

windows通过命令给文件夹或文件增加权限

给Demo001追加everyone权限 D:\cmd>cacls Demo001 /p everyone:f /e 处理的目录: D:\cmd\Demo001D:\cmd> 给Demo001下的所有文件追加everyone权限 D:\cmd>cacls Demo001 /p everyone:f /e /t 处理的目录: D:\cmd\Demo001 处理的目录: D:\cmd\Demo001\A 处理的文件:…

Linux基础知识——(2)vim编辑器

目录 1 vi和vim简介2 vim三种模式3 vim命令模式3.1 光标移动3.2 复制操作3.3 剪切/删除3.4 撤销/恢复3.5 光标的快速移动 4 模式间的切换5 命令行模式5 编辑模式6 其他6.1 vim的配置文件6.2 异常退出6.3 退出方式“:x”6.4 vi编辑模式下Backspace无法退格删除6.5 修改只读【rea…

采集淘宝天猫整店商品api(店铺列表、店铺所有商品)

返回数据&#xff1a;请求链接 {"items": {"shop_id": "336945718","page": "1","real_total_results": "75","total_results": "75","page_size": 10,"page_coun…

深度学习人脸表情识别算法 - opencv python 机器视觉 计算机竞赛

文章目录 0 前言1 技术介绍1.1 技术概括1.2 目前表情识别实现技术 2 实现效果3 深度学习表情识别实现过程3.1 网络架构3.2 数据3.3 实现流程3.4 部分实现代码 4 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 深度学习人脸表情识别系…

C++ DAY03 类与对象

概述 对象&#xff1a;真实存在的事物 类&#xff1a; 多个对象抽取其共同点形成的概念 静态特征提取出的概念称为成员变量, 又名属性 动态特征提取出的概念称为成员函数, 又名方法 类与对象的关系 在代码中先有类后有对象 一个类可以有多个对象 多个对象可以属于同一个…

.NET6使用MiniExcel根据数据源横向导出头部标题及数据

.NET6MiniExcel根据数据源横向导出头部标题 MiniExcel简单、高效避免OOM的.NET处理Excel查、写、填充数据工具。 特点: 低内存耗用&#xff0c;避免OOM、频繁 Full GC 情况 支持即时操作每行数据 兼具搭配 LINQ 延迟查询特性&#xff0c;能办到低消耗、快速分页等复杂查询 轻量…