1.什么是框架
框架就是开发大佬定制研发的应用骨架
,是一个半成品,它对基础的代码进行了封装并且提供一些API接口
,其他开发者只需要直接调用封装好的API接口即可,可以省去很多代码的编写,从而提高工作效率
2.什么是自动化框架以及它的作用
自动化测试框架基本架构:
2.1.自动化框架
自动化测试leader为了对一个系统做自动化测试而封装的一个代码主骨架,其他的自动化测试工程师只需要去调用这个骨架里面的方法就可以实施自动化测试,这个代码骨架就叫做自动化框架
2.2.自动化框架的作用
- 提高测试效率,降低维护成本
- 减少人工干预脚本因素
- 增加代码的可重用率
2.3.unittest、单元测试框架和自动化测试框架的关系
- 单元测试:指的是针对我们程序的最小单元(方法)进行测试
- unittest是他自动化框架的重要组成部分之一
- pom是自动化旷考的重要组成部分之一
- ddt数据驱动:自动化框架的重要组成部分之一
- 全局配置文件封装:自动化框架的重要组成部分之一
- selenium二次封装:自动化框架的重要组成部分之一
- 日志监控:自动化框架的重要的组成部分之一
断言,邮件发送…
3.单元测试框架对比
基于python语言:unittest和pytest
基于java语言:junit和testing
3.1.用例编写规则
3.1.1.unittest
提供了testCase测试用例,testsuites测试套件,testfixtures测试固件或夹具,testloader测试加载器,testrunner测试运行器
,必须遵守以下规则
- 测试文件必须
先导入import unittest
- 测试类必须
继承unittest.TestCase
- 测试方法必须以
test开头
3.1.2.pytest
python的第三方测试框架
,基于unittest的扩展框架
,必须遵守以下规则:
- 测试
文件名必须以test_开头或者是_test结尾
- 测试
类命名必须以Test开头
- 测试
方法必须以test开头
4.unittest框架主要做了什么?
- 测试发现:从多个py文件中收集并且加载测试用例
- 测试执行:将测试用例按照一定的顺序和条件去执行并且生成结果
- 测试判断:通过断言去判断结果是否正确
- 测试报告:统计测试进度,通过率,生成报告
5.Unittest重要组件
- Testcase
- TestSuite
- TestFixture
- TestRunner
- TestLoader
6.TestCase用法
import unittest
class EcshopLogin(unittest.TestCase):
# 测试用例
def test01_login(self):
print("test_Login")
# 测试用例
def test02_pass(self):
print("test_pass")
6.1.使用unittest命令行的方式运行(集成jenkins时候使用):
光标放到要测试的测试用例
上,就可单独执行该测试用例
,如果把光标放到要测试用例的所在类
上,此时执行的是这个类里所有的测试用例
也可以在命令行输入
python -m unittest -v ecshop_login.EcshopLogin
- python -m:
以脚本的方式去运行一个模块
- unittest -v :verbose意思就是
更详细的输出结果
(也可以不加-v) - scshop_login:python
模块名
- EcshopLogin:
类名
- 当然也可以在其后添加
.方法名
- -k:匹配模式:
(1)通配符:-k*_login
:表示必须以_login结尾的
(2)字符串:-l pass
:匹配字符串,只要包含pass
就可以了
6.2.使用unittest.main(),以模块的方式运行
不能点右键运行,否则还是只运行单个测试用例
需要配置环境
配置完成之后,再点main函数的运行,就可以执行全部的测试用例,还可以在命令行里执行python ecshop_login.py
7.读懂执行的结果
- .:成功
- F:失败
- E:错误
- S:跳过
import unittest
class EcshopLogin(unittest.TestCase):
# 测试用例
@unittest.skip("此用例不执行")
def test01_login(self):
print("test_Login")
self.assertTrue(0)
# 测试用例
def test02_pass(self):
print("test_pass")
if __name__ == '__main__':
print("----------------")
unittest.main()
8.用例执行的顺序
按照ASCII的规则:【0-9,A-Z,a-z】
9.框架底层原理
参数
module='__main__'
:测试用例所在的路径__main__表示当前你模块
defaultTest=None:
待测用例的名称,默认是所有argv=None
:接收外部的参数testRunner=None
:测试运行器,TextTestRunnertestLoader=loader.defaultTestLoader
:指定使用默认的测试用例加载器exit=True
:是否在测试完成之后结束python程序verbosity=1
:类似于命令行-v,有三个参数,0,1,2
,其中2表示以一种详细的方式
,0表示即使成功了都没有.来提醒你
,2表示成功了有.提醒你
,参数设置成1,测试通过会输出".",失败输出"f",参数设置成2,测试通过详细输出测试套件里运行的哪些测试方法和Okfailfast=None
:当失败的时候是否终止测试catchbreak=None
:是否捕获复制buffer=None
:是否捕获这个输出流wainings=None
:是否显示警告信息tb_locals=False
可以给main加上参数
if __name__ == '__main__':
print("----------------")
unittest.main(defaultTest=["EcshopLogin.test02_pass"], verbosity=2)
9.1.只运行部分用例使用testsuite(测试套件)
import os
import unittest
class EcshopLogin(unittest.TestCase):
# 测试用例
# @unittest.skip("此用例不执行")
def test01_login(self):
print("test_Login")
# self.assertTrue(0)
# 测试用例
def test02_pass(self):
print("test_pass")
def test03_change(self):
print("test_change")
if __name__ == '__main__':
suite = unittest.TestSuite()
suite.addTest(EcshopLogin("test01_login")) # 表示一个一个的去加载测试用例
suite.addTest(EcshopLogin("test02_pass"))
# 第一种加载方式
testcases = [EcshopLogin("test01_login"), EcshopLogin("test02_pass")]
# 第二种加载方式
# start_dir=os.getcwd表示获取当前文件夹的工作路径
# pattern='test*.py':找到这个文件夹下以test开头,以.py结尾的所有测试用例
testcases = unittest.defaultTestLoader.discover(start_dir=os.getcwd, pattern='test*.py')
# suite.addTests(testcases) # 表示加载所有的测试用例
# 第一种运行方法
# unittest.main(defaultTest='suite')
# 第二种运行方法
unittest.TextTestRunner().run(suite)
10.TestFixture测试夹具(测试固件)
夹具的二次封装!!!很重要!!!
- 模块my_unit
import unittest
class MyUnit(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
print("setUpClass:在每个类之前执行一次,如:创建数据库,生成日志对象")
def setUp(self):
print("\nsetUp:在测试用例之前的准备工作,如:打开浏览器,加载网页,注意:执行所有测试用例前都会执行一次")
def tearDown(self) -> None:
print("tearDown:在每个测试用例结束之后的扫尾的工作,如:关闭浏览器")
@classmethod # 装饰器,必须加上
def tearDownClass(cls) -> None:
print("tearDownClass:在每个类之后执行一次,如关闭数据库连接,销毁日志对象")
- 模块test1
from LearnUnittest.my_unit import MyUnit
class EcshopLogin(MyUnit):
def test_baili(self):
print("测试百丽")
def test_weiwei(self):
print("测试维维")
- 模块test2
from LearnUnittest.my_unit import MyUnit
class Test2(MyUnit):
def test_login(self):
print("测试登录")
def test_register(self):
print("测试注册")
- 模块all
import unittest
import os
if __name__ == '__main__':
suite = unittest.TestSuite()
testcases = unittest.defaultTestLoader.discover(os.getcwd(), "*.py")
suite.addTests(testcases)
unittest.TextTestRunner(verbosity=2).run(suite)
10.忽略测试用例
10.1.忽略单个测试用例
from LearnUnittest.my_unit import MyUnit
import unittest
class EcshopLogin(MyUnit):
age = 22
@unittest.skip("忽略理由")
def test_baili(self):
print("测试百丽")
# 条件忽略,当这个条件为true时,忽略
@unittest.skipIf(age>=18 and age<=25, "如果维维在18到25之间就忽略")
def test_weiwei(self):
print("测试维维")
# 条件忽略,当这个条件为假时,忽略
@unittest.skipUnless(age<18, "如果知聊成年,就忽略")
def test_zhiliao(self):
print("测试知聊")
10.2.忽略整个模块
from LearnUnittest.my_unit import MyUnit
import unittest
@unittest.skip("此模块暂时不测试")
class EcshopLogin(MyUnit):
def test_baili(self):
print("测试百丽")
def test_weiwei(self):
print("测试维维")
def test_zhiliao(self):
print("测试知聊")
11.断言
基本的断言方法提供了测试结果是True还是False。所有的断言方法都有一个msg参数,如果指定msg参数的值,则将该信息作为失败的错误信息返回。也就是给方法最后添加一个msg参数
(1)如果断言失败,则抛出一个AssertionError,并标识该测试为失败状态
(2)如果异常,则当做错误来处理
注意:以上两种方式的区别
(3)如果成功,则标识该测试为成功状态
from LearnUnittest.my_unit import MyUnit
class EcshopLogin(MyUnit):
def test_baili(self):
print("测试百丽")
self.assertEqual(1, 1)
def test_weiwei(self):
print("测试维维")
self.assertTrue(1)
def test_zhiliao(self):
print("测试知聊")
self.assertIn("a", "abc")
注意点:
- 断言成功看不到断言结果,断言失败会看到错误
12.批量执行用例生成html报告
先下载HTMLTestRunner.py放到python/Lib目录下
注意:这里的代码仅将第十个章节所写的少量代码进行了更改,具体更改内容如下
- my_unit模块
import unittest
class MyUnit(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
print("setUpClass:在每个类之前执行一次,如:创建数据库,生成日志对象")
def setUp(self):
print("\nsetUp:在测试用例之前的准备工作,如:打开浏览器,加载网页,注意:执行所有测试用例前都会执行一次")
def tearDown(self) -> None:
print("tearDown:在每个测试用例结束之后的扫尾的工作,如:关闭浏览器")
@classmethod # 装饰器,必须加上
def tearDownClass(cls) -> None:
print("tearDownClass:在每个类之后执行一次,如关闭数据库连接,销毁日志对象")
- test1模块
from LearnUnittest.my_unit import MyUnit
class EcshopLogin(MyUnit):
def test_baili(self):
"""测试百丽"""
print("测试百丽")
def test_weiwei(self):
"""测试维维"""
print("测试维维")
def test_zhiliao(self):
"""测试知聊"""
print("测试知聊")
self.assertEqual(1, 2)
- test2模块
from LearnUnittest.my_unit import MyUnit
class Test2(MyUnit):
def test_login(self):
"""测试登录"""
print("测试登录")
def test_register(self):
"""测试注册"""
print("测试注册")
- all模块
import unittest
import os
import time
from HTMLTestRunner import HTMLTestRunner
if __name__ == '__main__':
suite = unittest.TestSuite()
testcases = unittest.defaultTestLoader.discover(os.getcwd(), "*.py")
suite.addTests(testcases)
nowtime = time.strftime("%Y-%m-%d %H-%M-%S")
name = open(os.getcwd()+"/"+nowtime+"_report.html", "wb")
runner = HTMLTestRunner(stream=name, verbosity=2,
title="2021/04/24测试报告", description="报告详情如下:")
runner.run(suite)
此时,就生成了一份html报告
13.DDT数字驱动
13.1.为什么要数字驱动以及驱动模式介绍
数据文件有11组数据,代码只有一份,代码数据分离,解耦合
13.2.驱动模式介绍
- 数据驱动
- 关键字驱动(核心:把业务逻辑封装成关键字login,只需要调用login,就能达到登录的目的,把login叫做关键字)
- 混合驱动模式(关键字+数据驱动):市场主流
- 行为驱动测试(很少用到)
13.3.什么是DDT(数据驱动测试)
date driver test数据驱动测试
特点:可以完美和应用于Unittest框架实现数据结构
13.4.DDT详解
- ddt是通过装饰器的形式来调用的
- 装饰器:口红就是女人的装饰器。装饰器就是完成一种特定功能的函数(事务)
- 在python里面装饰器是以@开头,并且装饰器有两种:类装饰器,函数装饰器
13.5.DDT里面有哪些装饰器
- @ddt:类装饰器:声明当前类使用ddt框架
- @data:函数装饰器,用于给测试用例传输数据
- @unpack:函数装饰器,将传输的数据包解包,一般作用于元组或者是列表
- @file_data:函数装饰器,可直接读取yam/json文件
13.6.实战
(1)@ddt,@data
- 当传单个值时,所有类型都可以传,并且用例执行一次
- 如果说:@data传入多个值的时候,那么传几个值,用例就执行几次
import unittest
from ddt import ddt, data
@ddt
class Test(unittest.TestCase):
# python数据类型:数字(int,long,float,complex),字符串,列表list,元组tuple,集合set,字典dic
@data(10)
def test01_login(self, num):
print(num)
if __name__ == '__main__':
unittest.main()
(2)@unpack
import unittest
from ddt import ddt, data, unpack
@ddt
class Test(unittest.TestCase):
# python数据类型:数字(int,long,float,complex),字符串,列表list,元组tuple,集合set,字典dic
# @data(('hello', "world!"), ('hello', 'python'))
# @unpack
# def test01_login(self, args1, args2):
# print(args1, args2)
@data({"name": "darcy", "age": "18"}, {"name": "lyh", "age": "20"})
@unpack
def test01_login(self, name, age):
print(name, age)
if __name__ == '__main__':
unittest.main()
注意点:
- 如果是数字或字符串,那么不需要@unpack
- 如果是元组和列表的话,那么可以通过@unpack,但是参数的个数必须和解完包后的值的个数一致
- 如果是集合无法解包
- 如果是字典可以通过@unpack解包,但是参数的名称和个数必须和字典的键保持一致