Unittest自动化测试框架合集(全文带注释2w字教程)——从0到1学会unittest框架

是羽十八ya 发表于 2022/05/10 13:11:24 2022/05/10
【摘要】 本文通过理论讲解+大量实例演示,全面介绍了unittest测试框架的使用方法,从0到1学习自动化测试框架必备。

  “ 本文通过理论讲解+大量实例演示,全面介绍了unittest测试框架的使用方法,文章层层深入,环环相扣,建议按顺序阅读,如遇不清楚的地方可翻看上文注释部分,一般都会有详细标明,阅读过程中发现问题or错误欢迎私信博主交流改正,谢谢大家的宝贵建议!最后祝愿读者们都能成为自动化测试领域的佼佼者!”


PS:“ 文末有各个模块核心要点总结,可根据需求自由跳转!”


  • 博主文章的注释都是干货!每个代码段都有详细注释!
  • 重要的事情说三遍:一定要看注释!!!一定要看注释!!!一定要看注释!!!

目录

测试用例TestCase基本框架

1.基础框架

2.基础框架实例演示 

3.小结 

Fixture测试夹具的使用和setUp方法相关参数传递

1.Fixture夹具的使用(unittest中的特殊类方法)

2. Python关键知识(一)——为3.作铺垫

3. Python关键知识(二)——为4.作铺垫

4.setUp定义全局实例属性——为5.作铺垫

5.当局实例属性与方法间参数依赖

6.小结

跳过测试用例skip操作

1.跳过的类型

2.条件跳过参数的导入

3.测试用例之间参数联动判定跳过的方法

4.小结 

数据驱动测试DDT

1.数据驱动测试的基本概念、引读

 引读

 单一参数的数据驱动测试

 多参数的数据驱动测试(一个测试参数中含多个元素)

2.txt格式文件驱动

单一参数数据驱动

多参数数据驱动 

3.json格式文件驱动

单一参数数据驱动

多参数数据驱动(以列表形式存储多参数)

多参数数据驱动(以对象形式存储多参数)

 4.yaml格式文件驱动

单一参数数据驱动

多参数数据驱动

5.小结

测试套件TestSuite

1.引言

2.测试套件TestSuite基本框架

3.应用

4.装载器的使用

5.小结 

HTML测试报告生成

总结

1.测试用例TestCase基本框架

2.Fixture测试夹具的使用和setUp方法相关参数传递

3.跳过测试用例skip操作 

4.数据驱动测试DDT

5.测试套件TestSuite与HTML报告生成




测试用例TestCase基本框架

1.基础框架

import unittest

class BasicTestCase(unittest.TestCase):  # 设置基础测试类名,继承库中测试用例的属性
    # setUp()和tearDown()是每个测试用例进行时都会执行的测试方法,前者为起始,后者为结束
    # 程序执行流程:setUp()-test1()-tearDown()---setUp()-test2()-tearDown()---
    def setUp(self):  # 复写父类的方法,Pycharm环境左侧有标识,是每一个测试用例都会执行的"起始方法"
        pass  # 自定义设置开始步骤

    def tearDown(self):  # 复写父类的方法,Pycharm环境左侧有标识,是每一个测试用例都会执行的"结束方法"
        pass  # 自定义设置结束步骤

    def way(self):  # 根据实际需求编写的测试方法
        pass

    def test1(self):  # 设置测试用例1,命名为test+xxx,会按照test后的阿拉伯数字顺序执行,testdemo也执行,带test都会执行
        pass  # 执行程序+断言(自定义断言方法,灵活多变)

    def test2(self):  # 设置测试用例2
        pass  # 执行程序+断言(自定义断言方法,灵活多变)


if __name__ == '__main__':  # 设定条件执行unittest的主函数
    unittest.main()  # 调用主函数进行多个测试用例测试

2.基础框架实例演示 

  • 以测试自定义网站的登录功能为例
  • 代码详解
  • 框架可直接套用简单样例
  • 实际应用中,自定义步骤、断言方法灵活改变即可
import unittest
import time
from selenium import webdriver
from selenium.webdriver.chrome.service import Service

class BasicTestCase(unittest.TestCase):     # 设置基础测试类名,继承库中测试用例的属性

    # setUp()和tearDown()是每个测试用例进行时都会执行的测试方法,前者为起始,后者为结束
    def setUp(self):    # 复写父类的方法,最左侧有标识,是每一个测试用例都会执行的 起始方法
        # 自定义设置开始步骤
        ########################################
        # 示例:
        # 使用浏览器载入登录页面流程,每个测试用例都会执行
        # 注意驱动写作self.driver,否则会变成方法变量,而不是实例变量
        s = Service(r'F:\Download\Browser\Edge\chromedriver.exe')  # 设置驱动所在路径
        self.driver = webdriver.Chrome(service=s)  # 从路径提取驱动
        self.driver.implicitly_wait(10)  # 设置每个步骤最大等待时间
        self.driver.get('http://localhost:8001/login/')  # GET方法访问本机端口,即注册页面
        self.driver.maximize_window()   # 最大化窗口

    def tearDown(self):     # 复写父类的方法,最左侧有标识,是每一个测试用例都会执行的 结束方法
        # 自定义设置结束步骤
        ########################################
        # 示例:
        # 关闭浏览器页面,每个测试用例都会执行
        self.driver.close()

########################################################################################

    def input(self, user=None, password=None, vcode=None):  # 根据实际需求编写的测试方法
        ########################################
        # 示例:
        # 写入用户名,密码,验证码,并且点击登录
        if user:    # 设定判定条件,有输入才键入,否则不键入
            self.driver.find_element('id', 'LAY-user-login-username').send_keys(user)   # 将参数键入指定位置
        if password:
            self.driver.find_element('id', 'LAY-user-login-password').send_keys(password)
        if vcode:
            self.driver.find_element('id', 'LAY-user-login-vercode').send_keys(vcode)
        self.driver.find_element('id', 'loginButton').click()   # 点击登录按钮
        pass
########################################################################################
    def test1_login_success(self):    # 设置测试用例1,测试成功登录的情况,命名为test+xxx,会按照test后的阿拉伯数字顺序执行,testdemo也执行,带test都会执行
        # 执行+断言(自定义断言方法,灵活多变)
        ########################################
        # 示例:
        # 执行
        user, password, vcode = 'admin', '123456', '1234'   # 给定参数
        self.input(user, password, vcode)   # 传入参数给测试方法,并通过self.实例调用方法
        # 断言
        time.sleep(2)   # 断言之前要设置一定时间,以便浏览器反应
        # 注意此断言方法的文本属性.text
        self.assertEqual(self.driver.find_element('class name', 'layui-logo').text, '接口自动化测试')
        # 断言因素必须得设置注册页的元素而不是跳转后页面的元素

    def test2_errorpassword(self):    # 设置测试用例2,测试输入7位错误密码的情况
        # 执行+断言(自定义断言方法,灵活多变)
        ########################################
        # 示例:
        # 执行
        user, password, vcode = 'admin', '1234567', '1234'   # 给定参数
        self.input(user, password, vcode)   # 传入参数给测试方法,并通过self.实例调用方法
        # 断言
        time.sleep(1)   # 断言之前要设置一定时间,以便浏览器反应
        # 注意此断言方法的文本属性.text
        self.assertEqual(self.driver.find_element('class name', 'layui-layer-title').text, '出错了')


if __name__ == '__main__':  # 设定条件执行unittest的主函数
    unittest.main()     # 调用主函数进行多个测试用例测试

3.小结 

  • setUp()和tearDown()是每个测试用例进行时都会执行的测试方法,前者为起始,后者为结束
  • 命名为test+xxx,会按照test后的阿拉伯数字顺序执行,testdemo也执行,带test都会执行
  • 记得设定条件执行unittest的主函数
  • 示例框架可直接套用简单样例
  • 实际应用中,自定义步骤、断言方法灵活改变即可



Fixture测试夹具的使用和setUp方法相关参数传递

1.Fixture夹具的使用(unittest中的特殊类方法)

  • 特殊类方法setUpClass(),tearDownClass(),在所有测试用例(整体)前后执行
  • 作用,改变执行流程:
  • setUpClass()---setUp()-test1()-tearDown()---setUp()-test1()-tearDown()---tearDownClass()
  • 一定要认真看注释!!!
# 1.特殊类方法setUpClass(),tearDownClass(),在所有测试用例(整体)前后执行
# 1.作用,改变执行流程:setUpClass()---setUp()-test1()-tearDown()---setUp()-test1()-tearDown()---tearDownClass()

class BasicTestCase(unittest.TestCase):
    @classmethod  # 定义类方法
    def setUpClass(cls):  # 覆盖父类的类方法
        pass

    @classmethod  # 定义类方法
    def tearDownClass(cls):  # 覆盖父类的类方法
        pass

    def setUp(self):
        pass

    def tearDown(self):
        pass

    def test1(self):
        pass

    def test2(self):
        pass
if __name__ == '__main__':  # 设定条件执行unittest的主函数
    unittest.main()   

2. Python关键知识(一)——为3.作铺垫

  • 定义类属性,普通方法访问类属性需要通过类名访问,例如test1()中想要获取guide需要通过语句BasicTestCase.guide直接访问类属性
  • 语法块中高亮是因为这是在方法中定义的类属性,代码未运行前无法识别,运行时无影响
  • 注意与3.做比较
  • 一定要认真看注释!!!
# 2.定义类属性,普通方法访问类属性需要通过类名访问,例如test1()中想要获取guide需要通过语句BasicTestCase.guide直接访问类属性
# 2.高亮是因为这是在方法中定义的类属性,代码未运行前无法识别
# 2.注意与3.做比较

class BasicTestCase(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.guide = 'yu'  # 在类方法下,定义类属性cls.guide = 'yu'
        pass

    def test1(self):  # 设置测试用例1
        guide = BasicTestCase.guide     # 获取类方法中的类属性,通过类名.类属性访问BasicTestCase.guide


if __name__ == '__main__':  # 设定条件执行unittest的主函数
    unittest.main()   

3. Python关键知识(二)——为4.作铺垫

  • Python中的特性:如果实例没有相应属性,类属性有,则Python自动访问类属性替代

  • 注意与4.作比较

  • 一定要认真看注释!!!

# 3.Python中的特性:如果实例没有相应属性,类属性有,则Python自动访问类属性替代(注意与4.作比较)

class BasicTestCase(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.guide = 'yu'  # 在类方法下,定义类属性cls.guide = 'yu'
        pass

    def test1(self):
        guide = self.guide
        # 3.在这段话中,这句话也可以获取guide = 'yu',因为语句虽然为self.guide,实例没有定义guide属性,
        # 3.Python中的特性:如果实例没有属性,Python自动访问类属性替代


if __name__ == '__main__':  # 设定条件执行unittest的主函数
    unittest.main()   

4.setUp定义全局实例属性——为5.作铺垫

  • 在Unittest套件中,全局实例属性可以在setUp,tearDown中设置

  • 注意和当局实例属性区分,与5.作比较
  • 一定要认真看注释!!!
# 4.在Unittest套件中,全局实例属性可以在setUp,tearDown中设置,注意与5.做对比

class BasicTestCase(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.guide = 'yu'  # 在类方法下,定义类属性cls.guide = 'yu'
        pass

    def setUp(self):
        self.guide = 'ba'   # 在setUp()方法下,定义 全局实例属性self.guide = 'ba'

    def test1(self):
        guide = self.guide  # 3.在这段话中,这句话也获取guide = 'ba',因为实例在setUp中定义全局实例属性self.guide = 'ba'

if __name__ == '__main__':  # 设定条件执行unittest的主函数
    unittest.main()   

5.当局实例属性与方法间参数依赖

  • 普通方法(test1)只可定义"当局"实例属性,生命周期为本方法内,无法制造依赖关系
  • 即普通方法test2无法使用test1中定义的实例属性
  • 制造方法间依赖关系的手段:使用类属性在每一个方法里传参即可
  • 一定要认真看注释!!!
# 5.普通方法只可定义"当局"实例属性,生命周期为本方法内,无法制造依赖关系
# 5.制造方法间依赖关系的手段:使用类属性在每一个方法里传参即可

class BasicTestCase(unittest.TestCase):
    def setUp(self):
        self.guide = 'ba'   # 在setUp()方法下,定义"全局"实例属性self.guide = 'ba'

    def test1(self):
        guide = 'shi'  # 在test1中定义"当局"实例属性guide = 'shi'
        print(guide)   # 这里拿到的guide = 'shi'

    def test2(self):
        guide = self.guide
        print(guide)   # 这里拿到的guide = 'ba',而不是'shi',说明普通方法中的实例变量生命周期仅限"当局",无法互相依赖

if __name__ == '__main__':  # 设定条件执行unittest的主函数
    unittest.main()   

6.小结

  • 夹具(特殊类方法)setUpClass(),tearDownClass(),在所有测试用例(整体)前后执行
  • 在Unittest套件中,全局实例属性可以在setUp,tearDown中设置
  • 普通方法(test1)只可定义"当局"实例属性,生命周期为本方法内,无法制造依赖关系
  • 制造方法间参数依赖关系的手段:使用类属性在每一个方法里传参即可



跳过测试用例skip操作

1.跳过的类型

  • 直接跳过、条件跳过

  • 一定要认真看注释!!!
# 1.跳过语句:直接跳过、条件跳过
# @unittest.skip('跳过的原因')
# @unittest.skipIf('跳过条件', '跳过的原因')
# @unittest.skipUnless('不跳过的条件', '不跳过的原因')
# 实例演示:下列测试仅仅执行test3,因为test1跳过、test2满足跳过条件,test3满足不跳过条件
class BasicTestCase(unittest.TestCase):
    @unittest.skip('因为我想跳过所以跳过')  # 直接跳过
    def test1(self):
        print('执行test1')
    @unittest.skipIf(888 < 999, '因为888比999小,所以跳过')  # 条件性跳过
    def test2(self):
        print('执行test2')
    @unittest.skipUnless('你真厉害', '因为你真厉害,所以不跳过')
    def test3(self):
        print('执行test3')
if __name__ == '__main__':  # 设定执行unittest的主函数
    unittest.main()

2.条件跳过参数的导入

  • 必须在类下直接定义
  • 因为@unittest.skipIf()语句执行优先级大于所有def,即无论是setUp()、setUpClass()还是test2()都在其之后执行,所以定义必须在类下

  • 一定要认真看注释!!!
# 2.判定跳过的参数导入方法
# 2.必须在类下直接定义,如示例的number
# 2.因为@unittest.skipIf()语句执行优先级大于所有def,即无论是setUp()、setUpClass()还是test2()都在其之后执行,所以定义必须在类下
# 实例演示:
class BasicTestCase(unittest.TestCase):
     number = '888'
     @unittest.skipIf(number < '999', '因为number比999小,所以跳过')
     def test2(self):     # 不会被执行,因为888满足跳过的条件
         print('执行test2')    

3.测试用例之间参数联动判定跳过的方法

  • 使用测试用例之间(例如:test1()、test2())相关参数联动设定跳过的方法

  • 语句编码+类属性变量---类属性变量通常用列表、字典等,解决多条件依赖时方便

  • 一定要认真看注释!!!
# 3.使用tests之间相关参数联动设定跳过的方法,语句编码+类属性变量---变量通常用多值类型的结构(列表、字典等,因为可能依赖多个条件)
class BasicTestCase(unittest.TestCase):
    judge = {'first': 0}

    def test2(self):
        print('执行test2')
        BasicTestCase.judge['first'] = 888    # 更改下个测试所要依赖的变量值

    def test3(self):
        if BasicTestCase.judge['first'] == 888:   # 设定判定条件看是否需要跳过
            return    # 若满足条件则直接return结束,此test下的之后的语句均不执行
        # print('执行test3')  # 此段代码中这句话加与不加都并不会被执行,测试通过但执行语句并没有执行,因为根据依赖的条件test3已经结束

if __name__ == '__main__':  # 设定条件执行unittest的主函数
    unittest.main()

4.小结 

  • 跳过的类型:直接跳过、条件跳过

  • 条件跳过参数的导入,必须在类下直接定义

  • 测试用例之间参数联动判定跳过的方法:语句编码+类属性变量




数据驱动测试DDT

1.数据驱动测试的基本概念、引读

 引读

  • 当我们进行测试时遇到执行步骤相同,只需要改变入口参数的测试时,使用DDT可以简化代码
  • 一定要认真看注释!!!
# 示例:
# 首先,我们观察这三个测试用例,我们会发现,三个测试用例除了入口参数需要变化,
# 其测试执行语句都是相同的,因此,为了简化测试代码,我们可以使用数据驱动测试的理论将三个方法写作一个方法

# 未使用数据驱动测试的代码:
class BasicTestCase(unittest.TestCase):
    def test1(self, num1):
        num = num1 + 1
        print('number:', num)

    def test2(self, num2):
        num = num2 + 1
        print('number:', num)

    def test3(self, num3):
        num = num3 + 1
        print('number:', num)

# 使用数据驱动测试的代码,执行效果与上文代码相同此处只需要了解大概框架,详细步骤下文会解释
@ddt
class BasicTestCase(unittest.TestCase):
    @data('666', '777', '888')    
    def test(self, num):     
        print('数据驱动的number:', num)
  • 相信到这里读者对数据驱动已经有了一定了解
  • 接下来我们开始进入DDT的学习

 单一参数的数据驱动测试

  • 步骤:导包——设置@ddt装饰器——写入参数——形参传递——调用
  • 一定要认真看注释!!!
# 单一参数的数据驱动

# 前置步骤:
# 使用语句import unittest导入测试框架
# 使用语句from ddt import ddt, data导入单一参数的数据驱动需要的包

# 示例会执行三次test,参数分别为'666','777','888'

@ddt    # 设置@ddt装饰器
class BasicTestCase(unittest.TestCase):
    @data('666', '777', '888')    # 设置@data装饰器,并将传入参数写进括号
    def test(self, num):     # test入口设置形参
        print('数据驱动的number:', num)
# 程序会执行三次测试,入口参数分别为666、777、888,结果见下图

 多参数的数据驱动测试(一个测试参数中含多个元素)

  • 步骤:导包——设置@ddt装饰器——设置@unpack解包——写入参数——形参传递——调用
  • 一定要认真看注释!!!
# 多参数的数据驱动
# 在单一参数包的基础上,额外导入一个unpack的包,from ddt import ddt, data, unpack
# 步骤:导包——设置@ddt装饰器——设置@unpack解包——写入参数——形参传递——调用

@ddt  
class BasicTestCase(unittest.TestCase):
    @data(['张三', '18'], ['李四', '19'])  # 设置@data装饰器,并将同一组参数写进中括号[]
    @unpack  # 设置@unpack装饰器顺序解包,缺少解包则相当于name = ['张三', '18']
    def test(self, name, age):
        print('姓名:', name, '年龄:', age)
# 程序会执行两次测试,入口参数分别为['张三', '18'],['李四', '19'],测试结果见下图

 


  • 看到这里,笔者以及介绍完了数据驱动测试的基本概念,以及单参数、多参数数据驱动的区别,但是,我们是将数据和代码写在一起,接下来笔者会介绍数据驱动测试的核心理念——数据与代码分离!!!(数据和代码在不同的文件里,方便维护代码和快速修改数据)
  • 数据驱动测试的核心——数据与代码分离
  • 接下来介绍txt格式、json格式、yaml格式数据的单参数、多参数数据驱动方法

2.txt格式文件驱动

单一参数数据驱动

  • 核心:编写阅读数据文件的函数、@data入口参数加*读取
  • 一定要认真看注释!!!
# 单一参数txt文件
# 新建num文件,txt格式,按行存储777,888,999
# num文件内容(参数列表):
# 777
# 888
# 999
# 编辑阅读数据文件的函数
# 记住读取文件一定要设置编码方式,否则读取的汉字可能出现乱码!!!!!!
def read_num():
    lis = []    # 以列表形式存储数据,以便传入@data区域
    with open('num', 'r', encoding='utf-8') as file:    # 以只读'r',编码方式为'utf-8'的方式,打开文件'num',并命名为file
        for line in file.readlines():   # 循环按行读取文件的每一行
            lis.append(line.strip('\n'))  # 每读完一行将此行数据加入列表元素,记得元素要删除'/n'换行符!!!
        return lis    # 将列表返回,作为@data接收的内容
@ddt
class BasicTestCase(unittest.TestCase):
    @data(*read_num())  # 入口参数设定为read_num(),因为返回值是列表,所以加*表示逐个读取列表元素
    def test(self, num):
        print('数据驱动的number:', num)
# 测试结果见下图

 

多参数数据驱动 

  • 核心:读取函数中的数据分割、@unpack解包
  • 一定要认真看注释!!!
# 多参数txt文件
# dict文件内容(参数列表)(按行存储):
# 张三,18
# 李四,19
# 王五,20
def read_dict():
    lis = []  # 以列表形式存储数据,以便传入@data区域
    with open('dict', 'r', encoding='utf-8') as file:  # 以只读'r',编码方式为'utf-8'的方式,打开文件'num',并命名为file
        for line in file.readlines():  # 循环按行读取文件的每一行
            lis.append(line.strip('\n').split(','))  # 删除换行符后,列表为['张三,18', '李四,19', '王五,20']
            # 根据,分割后,列表为[['张三', '18'], ['李四', '19'], ['王五', '20']]
        return lis  # 将列表返回,作为@data接收的内容


@ddt
class BasicTestCase(unittest.TestCase):
    @data(*read_dict())  # 加*表示逐个读取列表元素,Python中可变参数,*表示逐个读取列表元素,列表为[['张三', '18'], ['李四', '19'], ['王五', '20']]
    @unpack  # 通过unpack解包,逐个传参,缺少这句会将['张三', '18']传给name,从而导致age为空
    def test(self, name, age):  # 设置两个接收参数的形参
        print('姓名为:', name, '年龄为:', age)
# 测试结果见下图

 


3.json格式文件驱动

单一参数数据驱动

  • 核心:使用json解析包读取文件
  • 一定要认真看注释!!!
# 单一参数——json文件
# num.json文件内容(参数列表)(注意命名后缀):
# ["666","777","888"]
# 注意JSON文件中,数据元素如果是字符串必须得用双引号
# 使用语句import json导入json包,快速读取文件用
def read_num_json():
    return json.load(open('num.json', 'r', encoding='utf-8'))  # 使用json包读取json文件,并作为返回值返回,注意读取的文件名
@ddt  # 数据驱动步骤和txt相同
class BasicTestCase(unittest.TestCase):
    @data(*read_num_json())
    def test(self, num):
        print('读取的数字是', num)

多参数数据驱动(以列表形式存储多参数)

  • 核心:@unpack装饰器的添加
  • 一定要认真看注释!!!
# 数据分离
# 多参数——json文件
# 步骤和单一参数类似,仅需加入@unpack装饰器以及多参数传参入口
# dict文件内容(参数列表)(非规范json文件格式):
# [["张三", "18"], ["李四", "19"], ["王五", "20"]]
# 注意json文件格式中字符串用双引号
def read_dict_json():
    return json.load(open('dict.json', 'r', encoding='utf-8'))  # 使用json包读取json文件,并作为返回值返回
@ddt
class BasicTestCase(unittest.TestCase):
    @data(*read_dict_json())
    @unpack     # 使用@unpack装饰器解包
    def test(self, name, age):    # 因为是非规范json格式,所以形参名无限制,下文会解释规范json格式 
        print('姓名:', name, '年龄:', age)
# 测试结果见下图

 

多参数数据驱动(以对象形式存储多参数)

  • 核心:形参名字与json中对象key名相同
  • 一定要认真看注释!!!
# 规范json格式读取,每一组参数以对象形式存储
# dict文件内容:
# [
#   {"name":"张三", "age":"18"},
#   {"name":"李四", "age":"19"},
#   {"name":"王五", "age":"20"}
# ]

def read_dict_json():
    return json.load(open('dictx.json', 'r', encoding='utf-8'))  # 使用json包读取json文件,并作为返回值返回
@ddt
class BasicTestCase(unittest.TestCase):
    @data(*read_dict_json())
    @unpack
    def test(self, name, age):    # 令形参名字和json中命名相同name=name,age=age
        print('姓名:', name, '年龄:', age)
# 测试结果见下图

# 非常特殊情况:
# 形参名字和json中对象命名无法相同,则更改读取函数
# 提取已读完后的json文件(字典形式),通过遍历获取元素,并返回
# def read_dict_json():
#     li = []
#     dic = json.load(open('dict.json', 'r', encoding='utf-8'))
#     # 此处加上遍历获取语句,下文yaml格式有实例,方法一样
#     return li

  


 4.yaml格式文件驱动

  • 在自动化测试领域中,yaml数据格式是举足轻重的,因此笔者在此特地进行yaml格式解析

  • 在unittest测试框架中,对yaml数据格式的支持十分强大,使用非常方便

  • yaml文件的数据驱动执行代码十分简单!!!(但是要注意细节)

  • 学习更多yaml数据格式的知识,读者可通过链接快速学习

yaml实例教程 http://t.csdn.cn/y8Okn

单一参数数据驱动

  • 核心:使用yaml解析包读取文件,导入file_fata驱动数据
  • 一定要认真看注释!!!
# YAML数据格式驱动
# 单一参数
# import yaml   # 导入yaml解析包
# from ddt import file_data     # 导入file_data驱动数据
# yaml格式文件内容
# - 666
# - 777
# - 888
# '-'号之后一定要打空格!!!
@ddt
class BasicTestCase(unittest.TestCase):
    @file_data('num.yml')   # 采用文件数据驱动
    def test(self, num):
        print('读取的数字是', num)
# 测试结果见下图

   

多参数数据驱动

  • 核心:形参入口和数据参数key命名统一(下文介绍参数无法统一的解决办法)
  • 一定要认真看注释!!!
# 多参数yaml
# 以对象形式存储yml数据(字典)
# yaml格式文件内容
# -
#   name: 张三
#   age: 18
# -
#   name: 李四
#   age: 19
# -
#   name: 王五
#   age: 20
# '-'号之后一定要打空格!!!
# ':'号之后一定要打空格!!!

# 入口参数与数据参数key命名统一即可导入
@ddt
class BasicTestCase(unittest.TestCase):
    @file_data('dict.yml')
    def test(self, name, age):  # 设置入口参数名字与数据参数命名相同即可
        print('姓名是:', name, '年龄为:', age)
# 测试结果见下图

  • 特殊情况:当入口与文件中数据参数无法统一命名时,解决办法 
# 入口参数与数据参数命名不统一
@ddt
class BasicTestCase(unittest.TestCase):
    @file_data('dict.yml')
    def test(self, **cdata):  # Python中可变参数传递的知识:**按对象顺序执行
        print('姓名是:', cdata['name'], '年龄为:', cdata['age'])    # 通过对象访问语法即可调用
  • Python中传递可变参数:*代表顺序阅读列表类型,**代表顺序阅读对象(字典)类型,点击阅读可变参数部分可了解相关机制

Python中可变参数传递机制 http://t.csdn.cn/8JZZ4


5.小结

  • 数据驱动:测试时遇到执行步骤相同,只需要改变入口参数的测试时,使用DDT可以简化代码

    • 单参数:步骤:导包——设置@ddt装饰器——写入参数——形参传递——调用

    • 多参数:步骤:导包——设置@ddt装饰器——设置@unpack解包——写入参数——形参传递——调用

  • txt文件数据驱动

    • 单参数核心:编写阅读数据文件的函数、@data入口参数加*读取

    • 多参数核心:读取函数中的数据分割、@unpack解包

  • json文件数据驱动

    • 单参数核心:json解析包读取文件

    • 多参数列表核心:json解析包读取文件,@unpack装饰器的添加

    • 多参数对象核心:json解析包读取文件,@unpack装饰器的添加形参名字与json中对象key名相同

  • yaml文件数据驱动

    • 单参数核心:使用yaml解析包读取文件,导入file_fata驱动数据

    • 多参数核心:形参入口和数据参数key命名统一





测试套件TestSuite

1.引言

  • 测试套件:执行已编写的测试用例,特点:可用于执行不同类测试用例之间指定测试用例组合测试,也可指定某类、某些类测试用例单独测试

  • 例如:

  • 登陆类测试用例:1.登录成功    2.登录失败

  • 账户类测试用例:1.用户A    2.用户B

  • 测试套件的功能:可单独执行登录类测试用例、执行用户A+登录成功的用例

  • 一定要认真看注释!!!
# 测试用例文件和测试套件文件要分开!!!
# 这里笔者将此测试用例命名为Testcases
# Testcase.py文件内容:
# 文件说明:Testcase.py中有两类测试用例(login和data类),每一类测试用例下有两个测试用例
import unittest
class Testcase1_login(unittest.TestCase):
    def test1(self):
        print('执行Testcase1_login的test1')
    def test2(self):
        print('执行Testcase1_login的test2')

class Testcase2_data(unittest.TestCase):
    def test1(self):
        print('执行Testcase2_data的test1')
    def test2(self):
        print('执行Testcase2_data的test2')

2.测试套件TestSuite基本框架

  • 核心:导入测试用例模块(自己编写的),类的实例化
  • 一定要认真看注释!!!
# 测试套件文件内容!!!
# 一定要记得和测试用例文件互相独立
import unittest
import Testcases    # 导入测试用例,这里模块名是自己建立的测试用例文件名
suite = unittest.TestSuite()    # 类的实例化!!!要加括号才是实例化

# 一次添加单个测试用例
suite.addTest(Testcases.Testcase1_login('test1'))     # 添加第1类测试用例中的第1个测试用例
suite.addTest(Testcases.Testcase2_data('test1'))     # 添加第2类测试用例中的第1次测试用例

r = unittest.TextTestRunner()     # 类的实例化!!!要加括号才是实例化,文本运行测试
r.run(suite)  # 运行测试套件

# 执行结果见下图
# 实现了指定测试用例的执行


3.应用

  • 一次添加多个测试用例,列表存储
  • 一定要认真看注释!!!
import unittest
import Testcases

suite = unittest.TestSuite()   
 
# 一次添加单个测试用例
# suite.addTest(Testcases.Testcase1_login('test1'))     # 添加第1类测试用例中的第1个测试用例
# suite.addTest(Testcases.Testcase2_data('test1'))     # 添加第2类测试用例中的第1次测试用例

# 一次添加多个测试用例,改为addTests,以列表形式存储,和上述方法等价,但简化了代码
# 注意调用的是addTests不是addTest
# Testcases.Testcase1_login('test1')表示文件Testcases类中子类Testcase_login的方法test1
suite.addTests([Testcases.Testcase1_login('test1'), Testcases.Testcase2_data('test1')])

r = unittest.TextTestRunner()     # 类的实例化!!!要加括号才是实例化,否则是一个类
r.run(suite)  # 运行套件
  • 以变量形式指定测试内容(推荐方法)
  • 一次添加多个测试用例,并增加程序可读性
  • 一定要认真看注释!!!
# 使程序更加可读,变量存储指定测试内容
suite = unittest.TestSuite()   

# 以变量形式存储指定测试内容,便于修改且增加程序可读性
tests = [
    Testcases.Testcase1_login('test1'),
    Testcases.Testcase2_data('test1')
]
suite.addTests(tests)

r = unittest.TextTestRunner()     
r.run(suite)  

4.装载器的使用

  • 装载器类型

  • loadTestsFromTestCase('测试用例的类名')

  • loadTestsFromModule('测试用例文件名/模块名')

  • loadTestsFromName('测试用例文件名/测试用例类名通用')

  • 使用类装载器,示例:

  • 一定要认真看注释!!!
# Testcase1_login()类下有两个用例test1、test2
# class Testcase1_login(unittest.TestCase):
#     def test1(self):
#         print('执行Testcase1_login的test1')
#     def test2(self):
#         print('执行Testcase1_login的test2')

####################################################
# 使用类装载器
# 一次执行一类测试用例
suite = unittest.TestSuite()
L = unittest.TestLoader()   # 装载器实例化,注意加()
suite.addTests(L.loadTestsFromTestCase(Testcases.Testcase1_login))  
# 设置加载方式为Testcase中login类测试用例,Case()待测试的类
run = unittest.TextTestRunner()
run.run(suite)

# 测试结果见下图

 

  • 使用模块装载器,示例:

  • 一定要认真看注释!!!
# Testcases文件下有两类测试用例,每一类中有两个测试用例,总共四个测试用例
class Testcase1_login(unittest.TestCase):
    def test1(self):
        print('执行Testcase1_login的test1')
    def test2(self):
        print('执行Testcase1_login的test2')

class Testcase2_data(unittest.TestCase):
    def test1(self):
        print('执行Testcase2_data的test1')
    def test2(self):
        print('执行Testcase2_data的test2')

# 一次执行一个模块的测试用例
suite = unittest.TestSuite()
L = unittest.TestLoader()   # 装载器实例化,注意加()
suite.addTests(L.loadTestsFromModule(Testcases))    # 从模块名载入
run = unittest.TextTestRunner()
run.run(suite)

# 测试结果见下图,执行了模块下的4次测试

 

  • 使用name装载器(推荐方法:类名/文件名通用),示例:

  • 一定要认真看注释!!!
# Testcases文件下有两类测试用例,每一类中有两个测试用例,总共四个测试用例
class Testcase1_login(unittest.TestCase):
    def test1(self):
        print('执行Testcase1_login的test1')
    def test2(self):
        print('执行Testcase1_login的test2')

class Testcase2_data(unittest.TestCase):
    def test1(self):
        print('执行Testcase2_data的test1')
    def test2(self):
        print('执行Testcase2_data的test2')

# 使用name装载器,类名/模块名通用
# 推荐
suite = unittest.TestSuite()
L = unittest.TestLoader()

suite.addTests(unittest.TestLoader().loadTestsFromName('Testcases'))    # 装载Testcases模块名
suite.addTests(unittest.TestLoader().loadTestsFromName('Testcases.Testcase1_login'))    # 装载Testcase1_login类名

run = unittest.TextTestRunner()    # 文本方式运行测试
run.run(suite)

# 测试结果见下图,执行6次测试

  • 模块过滤装载器,大型测试用例使用
  • unittext.defaultTestLoader.discover()

# 测试用例TestCases文件内容
# import unittest
# class Testcase1_login(unittest.TestCase):
#     def test1(self):
#         print('执行Testcase1_login的test1')
#     def test2(self):
#         print('执行Testcase1_login的test2')
# 
# class Testcase2_data(unittest.TestCase):
#     def test1(self):
#         print('执行Testcase2_data的test1')
#     def test2(self):
#         print('执行Testcase2_data的test2')

# 过滤装载器:defaultTestLoader.discover
all_tests = unittest.defaultTestLoader.discover(start_dir='./', pattern='Testcase*.py')
# start_dir筛选路径, pattern筛选文件名
# 默认装载器,可以执行指定路径,指定名字的测试模块,'./'表示当前文件夹,TestCase*.py表示前缀是Testcase的文件都会被执行
# 过滤为当前文件夹下以TestCase为前缀的用例,我们用例为TestCases且在当前文件夹,因此会执行
runner = unittest.TextTestRunner()
runner.run(all_tests)

# 测试结果见下图

 


5.小结 

  • 测试套件:可用于执行不同类测试用例之间指定测试用例组合
  • 测试指定用例组合:以变量形式指定测试内容(推荐方法,程序可读性高)
  • 测试指定类/指定模块用例组合:使用name装载器(推荐方法,类名/文件名通用)
  • 测试多模块大型测试用例合集,使用模块过滤装载器,设置筛选条件



HTML测试报告生成

  • HTML报告生成第三方包下载

下载链接 https://github.com/findyou/HTMLTestRunnerCN/tree/master/python3x

  •  CN结尾的生成中文报告,EN结尾的生成英文报告,下载到项目所在目录

      

  • 实例语法实例:
  • 仅改变测试执行器runner
# 测试用例TestCases文件内容
# import unittest
# class Testcase1_login(unittest.TestCase):
#     def test1(self):
#         print('执行Testcase1_login的test1')
#     def test2(self):
#         print('执行Testcase1_login的test2')
# 
# class Testcase2_data(unittest.TestCase):
#     def test1(self):
#         print('执行Testcase2_data的test1')
#     def test2(self):
#         print('执行Testcase2_data的test2')

# 前文提到过滤装载器的代码
# all_tests = unittest.defaultTestLoader.discover(start_dir='./', pattern='Testcase*.py')
# runner = unittest.TextTestRunner()
# runner.run(all_tests)

# 在过滤装载器代码基础上,改动runner部分生成HTML报告
# 笔者下载了HTMLTestReportCN为例,注意HTMLTestReportCN.py放在同一文件夹下
# 使用语句from HTMLTestReportCN import HTMLTestRunner导入生成HTML的Runner
all_tests = unittest.defaultTestLoader.discover(start_dir='./', pattern='Testcase*.py')
r = open('report_file.html', 'wb')  # 新建一个html文件并以二进制方式写入
runner = HTMLTestRunner(title='测试报告标题', description='测试描述', stream=r)   # 改变Runner,设置报告参数
runner.run(all_tests)
  •  当前目录自动生成report_file.html,打开查看内容:




总结

1.测试用例TestCase基本框架

  • setUp()和tearDown()是每个测试用例进行时都会执行的测试方法,前者为起始,后者为结束
  • 命名为test+xxx,会按照test后的阿拉伯数字顺序执行,testdemo也执行,带test都会执行
  • 记得设定条件执行unittest的主函数
  • 示例框架可直接套用简单样例
  • 实际应用中,自定义步骤、断言方法灵活改变即可

2.Fixture测试夹具的使用和setUp方法相关参数传递

  • 夹具(特殊类方法)setUpClass(),tearDownClass(),在所有测试用例(整体)前后执行
  • 在Unittest套件中,全局实例属性可以在setUp,tearDown中设置
  • 普通方法(test1)只可定义"当局"实例属性,生命周期为本方法内,无法制造依赖关系
  • 制造方法间参数依赖关系的手段:使用类属性在每一个方法里传参即可

3.跳过测试用例skip操作 

  • 跳过的类型:直接跳过、条件跳过

  • 条件跳过参数的导入,必须在类下直接定义

  • 测试用例之间参数联动判定跳过的方法:语句编码+类属性变量

4.数据驱动测试DDT

  • 数据驱动:测试时遇到执行步骤相同,只需要改变入口参数的测试时,使用DDT可以简化代码

    • 单参数:步骤:导包——设置@ddt装饰器——写入参数——形参传递——调用

    • 多参数:步骤:导包——设置@ddt装饰器——设置@unpack解包——写入参数——形参传递——调用

  • txt文件数据驱动

    • 单参数核心:编写阅读数据文件的函数、@data入口参数加*读取

    • 多参数核心:读取函数中的数据分割、@unpack解包

  • json文件数据驱动

    • 单参数核心:json解析包读取文件

    • 多参数列表核心:json解析包读取文件,@unpack装饰器的添加

    • 多参数对象核心:json解析包读取文件,@unpack装饰器的添加形参名字与json中对象key名相同

  • yaml文件数据驱动

    • 单参数核心:使用yaml解析包读取文件,导入file_fata驱动数据

    • 多参数核心:形参入口和数据参数key命名统一

5.测试套件TestSuite与HTML报告生成

  • 测试套件:可用于执行不同类测试用例之间指定测试用例组合
  • 测试指定用例组合:以变量形式指定测试内容(推荐方法,程序可读性高)
  • 测试指定类/指定模块用例组合:使用name装载器(推荐方法,类名/文件名通用)
  • 测试多模块大型测试用例合集,使用模块过滤装载器,设置筛选条件
  • HTML报告生成,改变Runner工具

💗 “三生——分享欲可以是爱意的表达,分享欲可以是高级的浪漫,分享与也可以是深入骨髓的温柔” 

                                                                                                                                   ——Created By 是羽十八ya



【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区),文章链接,文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件至:cloudbbs@huaweicloud.com进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。