“ 本文通过理论讲解+大量实例演示,全面介绍了unittest测试框架的使用方法,文章层层深入,环环相扣,建议按顺序阅读,如遇不清楚的地方可翻看上文注释部分,一般都会有详细标明,阅读过程中发现问题or错误欢迎私信博主交流改正,谢谢大家的宝贵建议!最后祝愿读者们都能成为自动化测试领域的佼佼者!”
PS:“ 文末有各个模块核心要点总结,可根据需求自由跳转!”
- 博主文章的注释都是干货!每个代码段都有详细注释,一定要认真看注释!!!
- 重要的事情说三遍:一定要看注释!!!一定要看注释!!!一定要看注释!!!
目录
1.Fixture夹具的使用(unittest中的特殊类方法)
2.Fixture测试夹具的使用和setUp方法相关参数传递
测试用例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.基础框架实例演示
- 以测试自定义网站的登录功能为例
- Demo演示
- 代码详解
- 框架可直接套用简单样例
- 实际应用中,自定义步骤、断言方法灵活改变即可
- 一定要认真看注释!!!
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解析包读取文件,导入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中传递可变参数:*代表顺序阅读列表类型,**代表顺序阅读对象(字典)类型,点击阅读可变参数部分可了解相关机制链接
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报告生成第三方包下载
网络异常,图片无法展示
|
- 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