扩展 Python 的 unittest 框架

news2024/10/6 18:33:58

目录

前言:

如何控制 unittest 用例执行的顺序呢?

方式 1,通过 TestSuite 类的 addTest 方法,按顺序加载测试用例

方式 2,通过修改函数名的方式

那就造个轮子吧


前言:

Python 的 unittest 模块提供了一个标准的单元测试框架,但它有一些限制,例如无法处理多线程测试和模拟网络请求等。在这篇文章中,我们将介绍如何扩展 Python 的 unittest 框架,以便能够进行多线程测试和模拟网络请求等操作。

unittest 是 Python 标准库自带的单元测试框架,是 Python 版本的 JUnit,关于 unittest 框架的使用,官方文档非常详细,网上也有不少好的教程,这里就不多说了。

本文主要分享在使用 unittest 的过程中,做的一些扩展尝试。先上一个例子。

import unittest

class TestLegion(unittest.TestCase):
    def test_create_legion(self):
        """创建军团

        :return:
        """

    def test_bless(self):
        """ 公会祈福

        :return:
        """

    def test_receive_bless_box(self):
        """ 领取祈福宝箱

        :return:
        """

    def test_quit_legion(self):
        """退出军团

        :return:
        """

这是一个标准的使用 unittest 进行测试的例子,写完后心里美滋滋,嗯,就按照这个顺序测就可以了。结果一运行。

执行的顺序乱了!!第一个执行的测试用例并不是创建军团,而是公会祈福,此时玩家还没创建军团,进行公会祈福的话会直接报错,导致用例失败。

到这里有些同学会想说,为什么要让测试用例之间有所依赖呢?

的确,如果完全没依赖,测试用例的执行顺序是不需要关注的。但是这样对于用例的设计和实现,要求就高了许多。而对游戏来说,一个系统内的操作,是有很大的关联性的。以军团为例,军团内的每个操作都有一个前提,你需要加入一个军团。所以要实现用例之间的完全解耦,需要每个用例开始之前,检测玩家的军团状态。

如果可以控制测试用例的执行顺序,按照功能玩法流程一遍走下来,节省的代码量是非常可观的,阅读测试用例也会清晰许多。

如何控制 unittest 用例执行的顺序呢?

我们先看看,unittest 是怎么样对用例进行排序的。在loader.pyloadTestsFromTestCase方法里边,调用了getTestCaseNames方法来获取测试用例的名称

def getTestCaseNames(self, testCaseClass):
    """Return a sorted sequence of method names found within testCaseClass
    """
    def isTestMethod(attrname, testCaseClass=testCaseClass,
                     prefix=self.testMethodPrefix):
        return attrname.startswith(prefix) and \
            callable(getattr(testCaseClass, attrname))
    testFnNames = list(filter(isTestMethod, dir(testCaseClass)))
    if self.sortTestMethodsUsing:
        testFnNames.sort(key=functools.cmp_to_key(self.sortTestMethodsUsing))
    return testFnNames

可以看到,getTestCaseNames方法对测试用例的名称进行了排序

testFnNames.sort(key=functools.cmp_to_key(self.sortTestMethodsUsing))

看看排序方法

def three_way_cmp(x, y):
    """Return -1 if x < y, 0 if x == y and 1 if x > y"""
    return (x > y) - (x < y)

根据排序规则,unittest 执行测试用例,默认是根据 ASCII 码的顺序加载测试用例,数字与字母的顺序为:0-9,A-Z,a-z。

做个实验:

import functools

case_names = ["test_buy_goods", "test_Battle", "test_apply", "test_1_apply"]

def three_way_cmp(x, y):
    """Return -1 if x < y, 0 if x == y and 1 if x > y"""
    return (x > y) - (x < y)

case_names.sort(key=functools.cmp_to_key(three_way_cmp))
print(case_names)

output:['test_1_apply', 'test_Battle', 'test_apply', 'test_buy_goods']

基于 unittest 的机制,如何控制用例执行顺序呢?查了一些网上的资料,主要介绍了两种方式:

方式 1,通过 TestSuite 类的 addTest 方法,按顺序加载测试用例

suite = unittest.TestSuite()
suite.addTest(TestLegion("test_create_legion"))
suite.addTest(TestLegion("test_bless"))
suite.addTest(TestLegion("test_receive_bless_box"))
suite.addTest(TestLegion("test_quit_legion"))
unittest.TextTestRunner(verbosity=3).run(suite)

方式 2,通过修改函数名的方式

class TestLegion(unittest.TestCase):
    def test_1_create_legion(self):
        """创建军团

        :return:
        """

    def test_2_bless(self):
        """ 公会祈福

        :return:
        """

    def test_3_receive_bless_box(self):
        """ 领取祈福宝箱

        :return:
        """

    def test_4_quit_legion(self):
        """退出军团

        :return:
        """

看起来都能满足需求,但是都不够好用,繁琐,代码不好维护。

那就造个轮子吧

如何在不改动代码的情况下,让测试用例按照编写的顺序依次执行呢?

方案就是,在测试类初始化的时候,将测试方法按照编写的顺序,自动依次重命名为 “test_1_create_legion”,“test_2_bless”,“test_3_receive_bless_box” 等等,从而实现控制测试用例的执行。

这就需要控制类的创建行为,Python 提供了一个非常强力的工具:元类,在元类的__new__方法中,我们可以获取类的全部成员函数,另外基于 Python3.6 的字典底层重构后,字典是有序的了,默认顺序和添加的顺序一致。所以我们拿到的测试用例,就和编写的顺序一致了。

接下来,就是按照顺序,依次改名了,定义一个全局的total_case_num变量,每次进行改名的时候,total_case_num递增 +1,作为用例的 id,加入到用例的名字当中。

@staticmethod
def modify_func_name(func):
    """修改函数名字,实现排序 eg test_fight ---> test_00001_fight

    :param func:
    :return:
    """
    case_id = Tool.create_case_id()
    setattr(func, CASE_ID_FLAG, case_id)
    if setting.sort_case:
        func_name = func.__name__.replace("test_", "test_{:05d}_".format(case_id))
    else:
        func_name = func.__name__
    return func_name

接下来是定义自己的 TestCase 类,继承unittest.TestCase,使用上边定义的元类

class _TestCase(unittest.TestCase, metaclass=Meta):
    def shortDescription(self):
        """覆盖父类的方法,获取函数的注释

        :return:
        """
        doc = self._testMethodDoc
        doc = doc and doc.split()[0].strip() or None
        return doc

最后一步,对 unittest 打一个猴子补丁,将unittest.TestCase替换为自定义的_TestCase

unittest.TestCase = _TestCase

看下运行效果,代码和本文开始的例子一样,只是多了一句 utx 库的导入。

import unittest
from utx import *

class TestLegion(unittest.TestCase):
    def test_create_legion(self):
        """创建军团

        :return:
        """

    def test_bless(self):
        """ 公会祈福

        :return:
        """

    def test_receive_bless_box(self):
        """ 领取祈福宝箱

        :return:
        """

    def test_quit_legion(self):
        """退出军团

        :return:
        """

运行效果:

执行顺序就和我们的预期一致了~

基于这一套,开始加上其他的一些扩展功能,比如

  • 用例自定义标签,可以运行指定标签的测试用例

    @unique
    class Tag(Enum):
    SMOKE = NewTag("冒烟")  # 冒烟测试标记,可以重命名,不要删除
    ALL = NewTag("完整")  # 完整测试标记,可以重命名,不要删除
    
    # 以下开始为扩展标签,自行调整
    V1_0_0 = NewTag("V1.0.0版本")
    V2_0_0 = NewTag("V2.0.0版本")
    
class TestLegion(unittest.TestCase):
    @tag(Tag.SMOKE)
    def test_create_legion(self):
        """测试创建军团

        :return:
        """
        print("创建军团") 

    @tag(Tag.V1_0_0, Tag.ALL)
    def test_quit_legion(self):
        """测试退出军团

        :return:
        """
        print("测试退出军团")
        assert 1 == 2
  • 数据驱动
class TestLegion(unittest.TestCase):

    @data(["gold", 100], ["diamond", 500])
    def test_bless(self, bless_type, cost):
        """测试公会祈福

        :param bless_type: 祈福类型
        :param cost: 消耗数量
        :return:
        """
        print(bless_type)
        print(cost)

    @data(10001, 10002, 10003)
    def test_receive_bless_box(self, box_id):
        """ 测试领取祈福宝箱

        :return:
        """
        print(box_id)

# 默认会解包测试数据来一一对应函数参数,可以使用unpack=False,不进行解包

class TestBattle(unittest.TestCase):
    @data({"gold": 1000, "diamond": 100}, {"gold": 2000, "diamond": 200}, unpack=False)
    def test_get_battle_reward(self, reward):
        """ 领取战斗奖励

        :return:
        """
        print(reward)
        print("获得的钻石数量是:{}".format(reward['diamond']))
  • 检测测试用例是否编写了说明描述
2017-11-03 12:00:19,334 WARNING legion.test_legion.test_bless没有用例描述
  • 执行测试用例的时候,显示执行进度
2019-03-13 18:46:13,810 INFO 开始测试,用例数量总共15个,跳过5个,实际运行10个
2019-03-13 18:46:13,910 INFO start to test battle.test_tattle.test_start_battle (1/10)
2019-03-13 18:46:14,010 INFO start to test battle.test_tattle.test_skill_buff (2/10)
2019-03-13 18:46:14,111 INFO start to test battle.test_tattle.test_normal_attack (3/10)
2019-03-13 18:46:14,211 INFO start to test battle.test_tattle.test_get_battle_reward (4/10)
2019-03-13 18:46:14,211 DEBUG 测试领取战斗奖励,获得的钻石数量是:100
2019-03-13 18:46:14,311 INFO start to test battle.test_tattle.test_get_battle_reward (5/10)
  • setting 类提供多个设置选项进行配置

    class setting:
    # 只运行的用例类型
    run_case = {Tag.SMOKE}
    
    # 开启用例排序
    sort_case = True
    
    # 每个用例的执行间隔,单位是秒
    execute_interval = 0.1
    
    # 开启检测用例描述
    check_case_doc = True
    
    # 显示完整用例名字(函数名字+参数信息)
    full_case_name = False
    
    # 测试报告显示的用例名字最大程度
    max_case_name_len = 80
    
    # 执行用例的时候,显示报错信息
    show_error_traceback = True
    
    # 测试报告样式1
    create_report_by_style_1 = True
    
    # 测试报告样式2
    create_report_by_style_2 = True
    

  作为一位过来人也是希望大家少走一些弯路

在这里我给大家分享一些自动化测试前进之路的必须品,希望能对你带来帮助。

(软件测试相关资料,自动化测试相关资料,技术问题答疑等等)

相信能使你更好的进步!

点击下方小卡片

 

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

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

相关文章

做好防雷检测的意义和作用

防雷检测是指对雷电防护装置的性能、质量和安全进行检测的活动&#xff0c;是保障人民生命财产和公共安全的重要措施。我国对防雷检测行业有明确的国家标准和管理办法&#xff0c;要求从事防雷检测的单位和人员具备相应的资质和能力&#xff0c;遵守相关的技术规范和规程&#…

Cpp 01 — namespace命名空间、C++的输入与输出、缺省参数、函数重载、引用、隐式类型转换

前言&#xff1a;本文章主要用于个人复习&#xff0c;追求简洁&#xff0c;感谢大家的参考、交流和搬运&#xff0c;后续可能会继续修改和完善。 因为是个人复习&#xff0c;会有部分压缩和省略。 一、namespace命名空间 C使用命名空间(namespace)来避免命名冲突。 在定义一个…

初阶C语言-分支和循环语句(下)

“花会沿途盛开&#xff0c;以后的路也是。” 今天我们一起来继续学完分支语句和循环语句。 分支和循环 3.循环语句3.4 do...while()循环3.4.1 do语句的用法 3.5关于循环的一些练习3.6 goto语句 3.循环语句 3.4 do…while()循环 3.4.1 do语句的用法 do循环语句;//当循环语句…

【unity】ShaderGraph学习笔记

【unity】ShaderGraph学习笔记 创建ShaderGraph 创建URP的shaderGraph文件 在Project面板里Create→ShaderGraph→URP→这里主要有几个选项 Lit Shader Graph&#xff1a;有光照三维着色器 Unlit Shader Graph&#xff1a;无光照三维着色器 Sprite Custom Lit Shader Gra…

【解决方案】医疗行业资产定位管理方案-蓝牙资产定位方案

蓝牙资产定位追踪技术已经开始应用于多种行业的资产、设备管理方面&#xff0c;有不少机构、企业开始尝试使用实时蓝牙定位系统对其各类资产、机器设备、工具、材料、产品等进行精准定位&#xff0c;提高各类资产、设备的管理和使用效率&#xff0c;从而节省运营成本。 医疗行业…

汽配企业建设数字化工厂的步骤是什么

随着信息技术的迅猛发展&#xff0c;汽车行业也面临着数字化转型的迫切需求。汽配企业作为汽车产业链上重要的一环&#xff0c;也需要积极采取措施&#xff0c;建设数字化工厂系统&#xff0c;以适应市场竞争的变化。下面将介绍建设汽配企业数字化工厂的具体步骤。 第一步&…

在niso ii中读写EPCS时出现“Can‘t open flash device”

在niso ii中读写EPCS时出现“Can’t open flash device”&#xff0c;即alt_flash_open_dev(EPCS_FLASH_NAME)函数返回0。 Cant open flash device函数alt_flash_open_dev(EPCS_FLASH_NAME)打开epcs不成功&#xff0c;返回为0&#xff0c;试了几次硬件重新烧入、重启开发板还是…

呕心沥血解决xampp启动mysql异常停止due to a blocked port, missing dependencies问题

Error: MySQL shutdown unexpectedly.<br> This may be due to a blocked port, missing dependencies 这一行就是我问题的开始。 原因是因为&#xff0c;之前一直使用xampp用作本地mysql的启动管理&#xff0c;是个很好用的工具&#xff0c;但是近日想要给一个项目配…

STM32速成笔记—串口IAP

本文涉及到串口通信和Flash知识&#xff0c;对于这部分知识不熟悉的小伙伴可以到博主STM32速成笔记专栏查看。 文章目录 一、串口IAP简介1.1 什么是IAP1.2 STM32下载程序 二、串口IAP有什么作用三、启动流程3.1 正常启动流程3.2 加入IAP后的启动流程 四、必备知识4.1 修改程序运…

日常开发中Git遇到的问题-记录贴

日常开发中Git遇到的问题 前言场景一&#xff1a;clone代码并进行开发完毕后&#xff0c;进行代码合并时要求在新分支下提交代码场景二&#xff1a;远程分支被删除后&#xff0c;本地分支缓存需要更新&#xff0c;防止提交错误 前言 本篇博客只是用来记录平时开发过程之中用gi…

从头开始:数据结构和算法入门(时间复杂度、空间复杂度)

目录 文章目录 前言 1.算法效率 1.1 如何衡量一个算法的好坏 1.2 算法的复杂度 2.时间复杂度 2.1 时间复杂度的概念 2.2 大O的渐进表示法 2.3常见时间复杂度计算 3.空间复杂度 4.常见复杂度对比 总结 前言 C语言的学习篇已经结束&#xff0c;今天开启新的篇章——数据结构和算…

Hive视图

hive的视图 简介 hive的视图简单理解为逻辑上的表hive只支持逻辑视图&#xff0c;不支持物化视图视图存在的意义 对数据进行局部暴露&#xff08;涉及隐私的数据不暴露&#xff09;简化复杂查询 创建视图&#xff1a; create view if not exists v_1 as select uid,movie f…

国内十大精准的现货黄金价格走势图软件最新排名(综合版)

选择国内现货黄金价格走势图软件时&#xff0c;需要考虑几个因素。首先&#xff0c;软件的稳定性和可靠性至关重要。应选择有良好声誉和长期稳定运行的平台&#xff0c;以确保价格数据的准确性和及时性。其次&#xff0c;要选择功能齐全的软件。较为优秀的软件应该提供多种技术…

C++学习——类和对象(三)

接着我们就继续学习我们C当中的相关的知识。 一&#xff1a;初始化列表 还记得我们之前讲过的构造函数吗&#xff1f;我们在构造函数的函数体里面可以对对象当中的属性进行初始化。但是作为我们的构造函数来说&#xff0c;初始化的方式并不只是在构造函数体当中进行赋值。我们…

Junit4+MultiThreadedTestRunner 并发测试

目录 前言&#xff1a; 具体步骤&#xff1a; 前言&#xff1a; 在进行软件测试时&#xff0c;我们需要确保应用程序在不同的并发情况下仍能正常运行。 最近要对一个类里的方法&#xff0c;进行压力测试。下面讲一下写出的 Junit4 的并发测试代码吧。如果要复用的话&#x…

通达信一看就懂的成交量指标公式_通达信公式

用法说明&#xff1a;黄色为立桩量&#xff0c;绿色长竖条为逃顶提示&#xff0c;蓝色与红箭头为突破&#xff0c;红色为牛&#xff0c;紫色为立桩三天法则成立&#xff0c;笑脸为寻底&#xff0c;棕色与绿箭头为破位&#xff0c;淡红色黑中线涨停&#xff0c;深绿色黑中线跌停…

yolov7裂缝检测

B站视频笔记。 1.首先到Github上找RoboFlow的仓库地址 该教程提供了传统算法比如Resnet、YOLO等&#xff0c;还有包含一些较新的算法。 2.通过Colab打开例程 可以直接通过Colab打开,还支持其他的打开方式&#xff0c;这里提供三种方式。 提示&#xff1a;点击授权即可。 …

dy设备deviceid iid注册分析

清楚缓存&#xff0c;重新打开app, 点击同意按钮&#xff0c;会触发设备注册&#xff1b; 很明显是一个post包&#xff0c;device_register 可以看到请求体加密了 那么 请求体是什么呢&#xff1f; 很老版本思路&#xff1a;都是直接明文注册 较老版本思路&#xff1a;在反编译…

二、SQL-5.DQL-8).案例练习

1、查询年龄为20,21,22,23岁的员工信息 select * from emp where age in(20, 21, 22, 23) and gender 女; 2、查询性别为男&#xff0c;并且年龄在20-40岁&#xff08;含&#xff09;以内的姓名为三个字的员工 select * from emp where gender 男 && age between 2…

如何从任何地方远程解决电脑问题?

​如何远程解决电脑问题&#xff1f; “嗨&#xff01;我有一台Windows 10家用电脑。我外出旅行&#xff0c;但我的家人告诉我我的电脑有一段时间无法正常工作。我该如何远程检查电脑并解决相应的问题&#xff1f;提前谢谢&#xff01;” 您是否正在寻找远程解决电…