源码教学:教你 30 行代码实现 ddt 模块

简介: 用 python 做过自动化的小伙伴,大多数都应该使用过 ddt 这个模块,不可否认 ddt 这个模块确实挺好用,可以自动根据用例数据,来生成测试用例,能够很方便的将测试数据和测试用例执行的逻辑进行分离。接下来就带大家一起自己,手把手撸出一个 ddt。

前言

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

1、DDT 的实现原理

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

源码教学:教你 30 行代码实现 ddt 模块

ddt 在使用时非常简洁,也就是两个装饰器,@ddt 这个装饰器装饰测试类,@data 这个装饰器装饰器用例方法并传入测试数据。这两个装饰器实现的效果就是根据传入的用例数据自动生成用例。具体是怎么实现的呢?其实实现的思路也特别的简单,也就两个步骤:
整理了一份大厂软件测试面试宝典pdf

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

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

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 的功能我们就完全实现了。

下面是一个完整的案例,大家可以复制过去运行,也可以自己去写一遍,还可以根据自己的一些需求进行自定义的扩展。整理了一份大厂软件测试面试宝典pdf

完整案例

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()
相关文章
|
4月前
|
JavaScript Java 测试技术
基于SpringBoot+Vue+uniapp的C语言在线评测系统的详细设计和实现(源码+lw+部署文档+讲解等)
基于SpringBoot+Vue+uniapp的C语言在线评测系统的详细设计和实现(源码+lw+部署文档+讲解等)
|
4月前
|
JavaScript Java 测试技术
基于SpringBoot+Vue的程序设计基础视频学习系统的详细设计和实现(源码+lw+部署文档+讲解等)
基于SpringBoot+Vue的程序设计基础视频学习系统的详细设计和实现(源码+lw+部署文档+讲解等)
27 1
|
6月前
|
Python
Python模块学习应用案例详解
了解Python模块的复用特性,通过示例学习如何使用math模块进行数学运算(如正弦、平方根)、datetime模块处理日期和时间、random模块生成随机数,以及os模块操作文件和目录。通过`import`导入模块,然后调用相关函数,实现功能。
27 1
|
6月前
|
设计模式 算法 程序员
代码之禅:从功能实现到艺术境界
【2月更文挑战第19天】 在编程世界里,每一行代码不仅仅是冷冰冰的字符组合,它们背后承载着程序员的智慧和创造力。本文将深入探讨如何将日常的编程工作提升至一种艺术境界,让代码不仅实现功能需求,还能反映出编写者的哲学思考和技术审美。我们将通过一系列实践策略和思维模式,探索如何编织出既高效又优雅的代码,使之成为技术与艺术完美结合的产物。
|
6月前
|
C++ Python
python工程中import要点提炼
python工程中import要点提炼
|
SQL JSON 机器人
pytest+yaml设计接口自动化框架过程记录(一步一步记录如何设计,完结撒花),源码提供,视频教程
pytest+yaml设计接口自动化框架过程记录(一步一步记录如何设计,完结撒花),源码提供,视频教程
|
测试技术
pytest学习和使用23-通俗易懂的聊聊allure常用特性集合及使用方法说明
pytest学习和使用23-通俗易懂的聊聊allure常用特性集合及使用方法说明
125 0
|
Java 测试技术 数据库连接
python接口自动化(二十一)--unittest简介(详解)
前边的随笔主要介绍的requests模块的有关知识个内容,接下来看一下python的单元测试框架unittest。熟悉 或者了解java 的小伙伴应该都清楚常见的单元测试框架 Junit 和 TestNG,这个招聘的需求上也是经常见到的。
221 1
python接口自动化(二十一)--unittest简介(详解)
|
网络协议 jenkins 测试技术
python接口自动化(二十五)--unittest断言——下(详解)
本篇还是回归到我们最初始的话题,想必大家都忘记了,没关系看这里:传送门 没错最初的话题就是登录,由于博客园的登录机制改变了,本篇以我找到的开源免费的登录API为案例,结合 unittest 框架写 2 个用例。同样我们先来看一下接口文档。
157 0
python接口自动化(二十五)--unittest断言——下(详解)
|
测试技术 Python
python接口自动化(二十三)--unittest断言——上(详解)
在测试用例中,执行完测试用例后,最后一步是判断测试结果是 pass 还是 fail,自动化测试脚本里面一般把这种生成测试结果的方法称为断言(assert)。用 unittest 组件测试用例的时候,断言的方法还是很多的,下面介绍几种常用的断 言方法:assertEqual、assertIn、assertTrue。想了解更多可以点击 传送门 看一下最后的小结有大致介绍。
261 0
python接口自动化(二十三)--unittest断言——上(详解)