Python自动化测试 unittest框架 python自带的一个单元测试框架,不用额外安装,即可直接使用。测试人员用来做自动化测试(接口/UI自动化),作为自动化测试的执行框架,即管理和执行测试用例。
使用UnitTest框架的原因
能够组织多个用例去执行:能把多个测试用例放在一起,一起去执行
提供丰富的断言方法:代替人工自动的判断实际结果和预期结果是否相符
能够生成测试报告
unittest中最核心的四个概念是:
test case===>测试用例, test suite===>测试套件,
test runnerr===>测试执行, test fixture===>测试夹具。
UnitTest框架核心要素(组成部分)
TestCase:测试用例,作用是用来书写真正的用例代码(脚本)
TestSuite:测试套件,作用是用来组装(打包)TestCase(测试用例)的,既可以将多个用例脚本文件组装到一起
TestRunner:测试执行(测试运行),作用是用来执行TestSuite(测试套件)的
TestLoader:测试加载,是对TestSuite(测试套件)功能的补充,也是用来 组装(打包)TestCase(测试用例)的
Fixture:测试夹具,是一种代码结构,相当于书写前置方法(执行用例之前的方法)代码和后置方法(执行用例之后)代码,即用例执行顺序为前置->用例->后置
1、TestCase 1、作用
用来书写真正的用例代码(脚本)
单独一个测试用例,也可以执行
2、定义测试用例的步骤
步骤1:导包—->import unittest
步骤2:定义测试类—->新建测试类 必须 继承unittest.TestCase类,习惯性类名以Test开头
步骤3:定义测试方法—->测试方法名称命名 必须 以test开头
步骤4:执行
3、注意事项
代码文件名,要满足标识符的规则:字母数字下划线组成,不能以数字开头
代码文件名,不要使用中文
4、执行测试用例的两种方式
方式1:使用pycharm在代码上点击鼠标右键,选择使用UnitTest运行
在类名后边右键运行:会执行测试类中所有的测试方法
在方法名后边右键运行:只执行当前的测试方法
方式2:调用unittest.main()来运行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 ''' 学习TestCase(测试用例)的使用 ''' import unittestclass TestDemo (unittest.TestCase): def test_method1 (self ): print ('测试方法一' ) def test_method2 (self ): print ('测试方法二' ) ''' 4、执行测试用例的4种方式 4.1 在类名或者方法名后边右键运行 4.1.1在类名后边右键运行:会执行测试类中所有的测试方法 4.1.2在方法名后边右键运行:只执行当前的测试方法 4.2 使用unittest.main()执行测试用例 ''' if __name__ == '__main__' : unittest.main()
6、可能出现的错误
(1)文件名包含中文
(2)右键运行 没有unit test for……
解决方案1:新建一个代码文件,将之前的代码复制过来
解决方案2:在主程序使用unittest.main()来执行
解决方案3:使用减号,将python中内容移除
2、TestSuite和TestRunner 1、TestSuite的作用
将多条用例脚本集合在一起就是套件,即TestSuite是用来组装用例的
2、将用例脚本集合到测试套件中的步骤
1 2 3 4 5 6 7 8 9 10 步骤1 : 导包--->import unittest 步骤2 :实例化套件对象---> suite = unittest.TestSuite() 步骤3 :添加用例方法 (1 )一次添加一个测试方法--->suite.addTest(ClassName(‘MethodName’)) (2 ) 一次添加整个测试类--->suite .addTest(unittest.makeSuite(测试类名)) (3 )一次添加整个测试类 suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(ClassName))
3、TestRunner的作用
4、执行对象执行测试套件的步骤
步骤1: 导包—->import unittest
步骤2:实例化执行对象(运行对象)—-> runner = unittest.TextTestRunner()
步骤:3:执行对象执行套件对象—-> runner.run(suite)
#执行对象.run(套件对象)
5、整体步骤
步骤1: 导包—->import unittest
步骤2: 实例化套件对象—-> suite = unittest.TestSuite()
步骤3:添加用例方法
步骤4:实例化执行对象(运行对象)—-> runner = unittest.TextTestRunner()
步骤:5:执行对象执行套件对象—-> runner.run(suite)
6、注意事项
TextSuite需要配合TestRunner才能被执行
7、举例说明
(1)需求
创建2个文件,并在每个文件中定义1个测试类和2个测试方法
批量执行所有的测试用例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import unittest from hm_02_testcase1 import TestDemo1from hm_02_testcase2 import TestDemo2 suite = unittest.TestSuite() ''' 步骤3:添加用例方法 3.1 方式1:一次添加一个测试方法--->套件对象.addTest(测试类名('测试方法名')) 3.2 方式2:一次添加整个测试类->套件对象.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(ClassName)) #把指定测试类中的测试方法全部添加到测试套件中 3.3 方式3:一次添加整个测试类--->套件对象.addTest(unittest.makeSuite(测试类名)) #在不同的Python版本中,可能没有提示 ''' suite.addTest(TestDemo1('test_method1' )) suite.addTest(TestDemo1('test_method2' )) suite.addTest(TestDemo2('test_method1' )) suite.addTest(TestDemo2('test_method2' )) suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestDemo1)) suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestDemo2)) suite.addTest(unittest.makeSuite(TestDemo1)) suite.addTest(unittest.makeSuite(TestDemo2)) runner = unittest.TextTestRunner()
8、test suite(测试套件)、test runne(测试执行)==>runCase
1 2 3 4 5 6 7 8 9 10 概念: test suite 测试套件:把需要执行的测试用例放到一个套子 test runner 测试执行:使用测试执行去执行所有在套子里面的测试用例 语法: 测试套件:适合作为调试测试用例 suite = unittest.TestSuite() suite.addTest(测试类('测试方法' )) 测试执行: runner = unittest.TextTestRunner() runner.run(suite)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import unittestfrom Case.test_03_用例执行顺序 import TestLoginPayfrom Case.test_04_断言 import TestLogin03 suite = unittest.TestSuite() suite.addTest(TestLoginPay('test_01_login' )) suite.addTest(TestLoginPay("test_04_create_order" )) suite.addTest(TestLogin03("test_login" )) runner = unittest.TextTestRunner() runner.run(suite)
9、执行所有==>runAllcase
1 2 3 4 语法: test suite: unittest.defaultTestLoader.discover(start_dir=测试用例的目录,pattern=选取 测试用例执行的规则)
1 2 3 4 5 6 7 8 #执行所有case代码实现 import unittest # 添加所有的测试用例套件 suite = unittest.defaultTestLoader.discover("./Case",pattern="test_03*.py") # 实例化一个测试执行 runner = unittest.TextTestRunner() # 执行测试 runner.run(suite)
10、 测试执行==>test runner
1 2 3 4 5 概念: HTMLTestRunner:第三方插件 下载resource里面的HTMLTestRunnerPlugins放到python安装目录下的 lib 目录下 1. 生成的报告应该放在Report里面 2. 生成的报告应该以 .html结尾
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import unittestimport HTMLTestRunnerPluginsimport osimport timereport_path = os.path.dirname(__file__) + '/Report/' now = time.strftime('%Y-%m-%d %H_%M_%S' ) report_name = report_path + now + 'HTMLReport.html' suite = unittest.defaultTestLoader.discover('./Case' ) with open (report_name, 'wb' ) as fq: runner = HTMLTestRunnerPlugins.HTMLTestRunner( stream=fq, verbosity=2 , title='web自动化测试报告' , description="执行完所有的测试用例" , tester='第一组成员' ) runner.run(suite)
11、数据驱动
1 2 3 4 5 6 7 8 9 10 11 12 13 14 概念: 数据驱动:有的用例只是参数不一样,其他代码完全一样,就可以通过改变测试参数来实现一条 用例方法执行多种不同的测试场景.在unittest里面需要使用 ddt 来提供参数化的功能 pip install ddt 安装 语法: ddt可以参数化读取列表嵌套列表或者列表嵌套字典的数据 列表嵌套列表读取: 先在类前面加上: @ddt.ddt 在方法前面加上: @ddt.data(*列表嵌套列表) @ddt.unpack 列表嵌套字典读取: 先在类前面加上: @ddt.ddt 在方法前面加上: @ddt.data(*列表嵌套列表) 在方法里面使用一个形式参数来接收
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 import unittestimport ddtfrom Common.OperationData import OperationDatafrom Page.Login_Page import LoginPage data_list = OperationData('user_data.xls' ).get_data_list() data_dict = OperationData('user_data.xls' ).get_data_dict() @ddt.ddt class TestLoginDDt (unittest.TestCase): @ddt.data(*data_dict ) def test_login_dict (self, dict_data ): print (dict_data) login = LoginPage("chrome" ) login.open_login_url() login.send_user_name(dict_data['username' ]) login.send_password(dict_data['password' ]) login.click_login() if str (dict_data["expected" ]) == '1' : print ("判断表达式:" ,login.login_after_assert()) self.assertTrue(login.login_after_assert(),msg="登录失败" ) elif str (dict_data["expected" ]) == '0' : self.assertTrue(not login.login_after_assert(), msg="登录失败" ) else : print ("预期结果超过限制" ) if __name__ == '__main__' : unittest.main()
3、TestLoader TestLoader:测试加载
1、作用
作用和 TestSuite一样,也是 用来组装测试用例的,同样需要使用 TextTestRunner()去 执行
2、为什么有了TestSuite还要用TestLoader?
TestSuite:假如有10个用例脚本,通过makeSuite(测试类名)也需要10次才能将所有用例脚本都添加到测试套件中
TestLoader:使用unittest.TestLoader,通过该类下面的discover()方法自动搜索指定目录下指定开头的.py文件,并将查找到的测试用例组装到套件中 ,一行代码就可以将10个用例脚本都添加到套件中
结论:使用 TestLoader加载用例脚本更方便
3、通过TestLoader批量添加用例方法到套件中的步骤
步骤1: 导包—->import unittest
步骤2:实例化加载对象 并加载用例— -> 得到的是套件对象
suite = unittest.TestLoader().discover(‘用例所在的目录’,’用例代码文件名*.py’)
4、注意事项
unittest.TestLoader().discover( ‘用例所在的目录’,’用例代码文件名*.py’ )最终返回的还是套件对象,执行套件对象需要使用TestRunner
5、TestLoader与TestSuite区别
4、Fixture 1、Fixture引入案例
tpshop网页登录用例:
1、打开浏览器(一般只打开一次)
2、打开网页,点击登录(每次)
3、输入用户名、密码、验证码,点击【登录】(每次)
4、关闭页面(每次)
5、关闭浏览器(一般只关闭一次)
\ 如果有3条用例:**
1、打开浏览器(一般只打开一次,类,使用类 级别 Fixture)
2、打开网页,点击登录(每次)
3、输入用户名1、密码1、验证码1,点击【登录】(每次,测试方法,使用 方法级别 Fixture)
4、关闭页面(每次)
2、打开网页,点击登录(每次)
3、输入用户名2、密码2、验证码2,点击【登录】(每次,测试方法,使用 方法级别 Fixture)
4、关闭页面(每次)
2、打开网页,点击登录(每次)
3、输入用户名3、密码3、验证码3,点击【登录】(每次,测试方法,使用 方法级别 Fixture)
4、关闭页面(每次)
5、关闭浏览器(一般只关闭一次,类,使用类 级别 Fixture)
\ 结论:**
对于1、打开浏览器,2、打开网页,3、关闭网页,4、关闭浏览器,每个测试方法中都需要写,很麻烦,使用Fixture的话,这部分代码只写一次就可以了,执行每个测试用例方法前后都会自动执行Fixture写的2、4、这部分的代码
2、Fixture介绍
(1)作用
是一种代码结构,会在用例执行前后自动执行
简单理解就是对一个测试用例环境的初始化和销毁
(2)Fixture控制级别
方法级别 Fixture:在每个用例执行前、后都会自动调用,方法名是固定的
类级别 Fixture:在类中所有的测试方法执行前、后会自动执行的代码,一个类中只执行一次,类级别的Fixture需要写作类方法
模块级别 Fixture (了解):模块就是代码文件,模块级别就是在这个代码文件执行前、后会自动执行的代码,在类外部定义函数
3、方法级别Fixture
(1)使用方式
初始化(前置处理)
def setUp(self): #每个用例执行之前都会自动调用
销毁(后置处理)
def tearDown(self): #每个用例执行之后都会自动调用
(2)总结
运行于测试方法的始末,即:运行一次用例就会运行一次setUp和tearDown
4、类级别Fixture
(1)使用方式
初始化(前置处理)
@classmethod
def setUpClass(cls): #类前置方法,方法名不能变,因为这里相当于覆盖式重写了父类unittest中的setUpClass方法
销毁(后置处理)
@classmethod
def tearDownClass(cls): #类后置方法
(2)总结
运行于测试类的始末,即:每个测试类只会运行一次setUpClass和tearDownClass
类前置 方法前置 用例 方法后置 方法前置 用例 方法后置 类后置
5、模块级别Fixture(了解)
(1)使用方式
初始化(前置处理)
def setUpModule(): #首先自动执行
销毁(后置处理)
def tearDownModule(): #最后自动执行
(2)总结
运行于整个模块的始末,即:整个模块只会运行一次setUpModule和tearDownModule
6、注意
Fixture不一定是成对出现的,需要前置写前置,需要后置写后置即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import unittestclass TestLogin (unittest.TestCase): def setUp (self ) -> None : print ('2、打开网页,点击登录' ) def tearDown (self ) -> None : print ('4、关闭网页' ) @classmethod def setUpClass (cls ) -> None : print ('1、打开浏览器' ) @classmethod def tearDownClass (cls ) -> None : print ('5、关闭浏览器' ) def test_1 (self ): print ('3、输入用户名1、密码1、验证码1,点击【登录】' ) def test_2 (self ): print ('3、输入用户名2、密码2、验证码2,点击【登录】' ) def test_3 (self ): print ('3、输入用户名3、密码3、验证码3,点击【登录】' )
1 2 3 4 5 6 7 8 9 概念: test fixture==>测试夹具 语法 方法级别: setup() 在每一个测试方法执行之前执行 setup 的代码 teardown() 在每一个测试方法执行之后执行 teardown 的代码 类级别: setupClass() 在每一个测试类执行之前执行的方法 需要使用@classmethod 装饰 teardownClass() 在每一个测试类执行之后执行的方法 需要使用@classmethod 装饰
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import unittestclass TestLogin (unittest.TestCase): def test_login (self ): """普通登录测试""" print ("普通登录测试" ) def test_login_remember (self ): """记住密码测试""" print ("记住密码测试" ) def setUp (self ) -> None : print ("在每一个测试方法执行之前执行" ) def tearDown (self ) -> None : print ("在每一个测试方法执行之后执行" ) @classmethod def setUpClass (cls ) -> None : print ("在每一个测试类执行之前执行的方法,需要使用@classmethod装饰" ) @classmethod def tearDownClass (cls ) -> None : print ("在每一个测试类执行之后执行的方法,需要使用@classmethod装饰" ) if __name__ == '__main__' : unittest.main()
1 2 3 4 5 6 7 断言:判断预期和实际结果是否相符合 self.assertEqual(a,b,msg='错误描述' ) ==> 判断 a==b成立则测试用例通过 self.assertTrue(x,msg='错误描述' ) ==> 判断 表达式x 是否为true,为true通过测试 self.assertIn(a,b,msg='错误描述' ) ==> 判断 a in b成立则测试通过
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import unittestclass TestLogin03 (unittest.TestCase): def test_login (self ): expected = 'admin' result = "admin" print ("登录测试" ) self.assertEqual(expected,result, msg='用户名输入错误' ) def test_add_to_cart (self ): expected = 12 result = 12 print ("加入购物车的界面" ) self.assertTrue(expected == result,msg='数量不对' ) def test_create_order (self ): expected = '230527' result = 'tbd230527' print ("创建订单的界面" ) self.assertIn(expected,result,msg="订单号错误" ) if __name__ == '__main__' : unittest.main()
1 2 3 4 5 6 7 8 9 跳过测试 概念 1. 当我们写的部分用例,在某些情况下不需要执行的时候可以跳过 2. 当系统更新之后,部分的测试用例失效,但是不确定后面是否还会再改回来,就直接跳过 语法 装饰器实现的跳过测试用例 @unittest.skip(原因 ) ==> 没有条件,直接跳过 @unittest.skipIf(表达式,原因 ) ==> 表达式为真跳过测试 @unittest.skipUnless(表达式,原因 ) ==> 表达式为假跳过测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import unittestimport randomsex = random.randint(0 ,1 ) class TestLoginPay (unittest.TestCase): def test_01_page_enter (self ): print ("进入商品界面" ) @unittest.skip(reason='不想浏览' ) def test_02_browse_products (self ): print ("浏览商品的界面" ) @unittest.skipIf(1 ==1 ,reason='不想加入购物车' ) def test_03_add_to_cart (self ): print ("加入购物车的界面" ) def test_04_create_order (self ): print ("创建订单的界面" ) @unittest.skipUnless(sex==1 , reason='不想支付订单' ) def test_05_pay_order (self ): print ("支付订单的界面" ) if __name__ == '__main__' : unittest.main()
六、练习1
1、需求
创建一个目录case,作用就是用来存放用例脚本
在case目录中创建5个用例代码文件,test_case1.py……
使用TestLoader去执行用例
2、注意
3、实现代码
七、练习2
1、需求
定义一个tools模块,在这个模块中定义add方法,add方法可以对两个数字求和,返回求和结果
1 2 def add (a,b ): return a+b
这个add函数相当于开发的功能代码,进行自动化测试时可以调用开发的代码,但是不能修改开发的代码
书写用例,通过多组数据对add()函数进行测试
2、代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import unittestfrom lx2.tools import add class TestAdd (unittest.TestCase): def test_tools_add1 (self ): if 2 == add(1 , 1 ): print (f'用例{1 } {1 } {2 } 测试通过' ) else : print (f'用例{1 } {1 } {2 } 测试不通过' ) def test_tools_add2 (self ): if 3 == add(1 , 2 ): print (f'用例{1 } {2 } {3 } 测试通过' ) else : print (f'用例{1 } {2 } {3 } 测试不通过' ) def test_tools_add3 (self ): if 7 == add(3 , 4 ): print (f'用例{3 } {4 } {7 } 测试通过' ) else : print (f'用例{3 } {4 } {7 } 测试不通过' ) def test_tools_add4 (self ): if 9 == add(4 , 5 ): print (f'用例{4 } {5 } {9 } 测试通过' ) else : print (f'用例{4 } {5 } {9 } 测试不通过' )
总结一下 :
unittest是Python自带的单元测试框架,我们可以用其来作为我们自动化测试框架的用例组织执行框架。
unittest的流程:写好TestCase,然后由TestLoader加载TestCase到TestSuite,然后由TextTestRunner来运行TestSuite,运行的结果保存在TextTestResult中,我们通过命令行或者unittest.main()执行时,main会调用TextTestRunner中的run来执行,或者我们可以直接通过TextTestRunner来执行用例。
一个class继承unittest.TestCase即是一个TestCase,其中以 test
开头的方法在load时被加载为一个真正的TestCase。
verbosity参数可以控制执行结果的输出,0
是简单报告、1
是一般报告、2
是详细报告。
可以通过addTest和addTests向suite中添加case或suite,可以用TestLoader的loadTestsFrom__()方法。
用 setUp()
、tearDown()
、setUpClass()
以及 tearDownClass()
可以在用例执行前布置环境,以及在用例执行后清理环境
我们可以通过skip,skipIf,skipUnless装饰器跳过某个case,或者用TestCase.skipTest方法。
参数中加stream,可以将报告输出到文件:可以用TextTestRunner输出txt报告,以及可以用HTMLTestRunner输出html报告。
pytest框架 pytest
简单灵活,容易上手;支持参数化; 测试用例的skip和xfail 处理;
能够支持简单的单元测试和复杂的功能测试,还可以用来做 selenium/appium等自动化测试、接口自动化测试 (pytest+requests);
pytest具有很多第三方插件,并且可以自定义扩展, 比较好 用的如 pytest-allure(完美html测试报告生成) pytest-xdist (多CPU分发)等;
可以很好的和jenkins集成;
等等
安装
导入相关依赖库
1 2 3 4 5 6 7 8 9 10 pip install –U pytest 安装pytest,U表示升级,也可取消-U pip install sugar pip install pytest-rerunfailures pip install pytest-xdist pip install pytest-assume pip install pytest-html … pip list查看 pytest --version pytest –h 帮助
pytest的框架结构 Import pytest 类似的setup,teardown同样更灵活,还有个session()
模块级 (setup_module/teardown_module) 不在类中的函数有用 函数级 (setup_function/teardown_function) 不在类中的函数有用 类级 (setup_class/teardown_class)只在 类中前后运行一次。 方法级 (setup_method/teardown_methond) 运行在类中方法始末**
执行方式
pytest的执行方式 Pytest/py.test(终端,命令行,pycharm可配置pytest方式执行)
Pytest –v (最高级别信息—verbose)
pytest -v -s filename 3.Pytest-q (静默) (输出打印) 多种执行方式 1.pytest将在当前目录及其子目录中运行test _ .py或 test.py形 式的所有文件。* 2.以test开头的函数,以Test开头的类,以test开头的方法。所有包 package都要有_init.py文件。 3.Pytest可以执行unittest框架写的用例和方法
pytets执行测试用例
1:py测试文件必须以test(test_xxx)开头(或者以test结尾) 2:测试类必须以Test开头,并且不能有init方法——-测试类Test开头 3:测试方法必须以test开头 4:断言必须使用assert
一般做项目是新建package包
项目文件 lib库文件(登录接口源代码,其他接口公共的类,封装的库,登录的,订单的)(包) data文件(参数化数据,excel文件,yaml文件,csv文件—-测试文件,用例,文档)(可以是普通文件夹) test_case文件(放测试用例的 )(包) test_func01.py(测试用例,写的最好见名知意) report文件(存放测试报告的普通文件夹) config(配置文件)
pytest标记 由于某种原因(如test_func2的功能尚未开发完成),我们只想执行指定的测试函数。在pytest中有几种方式可以解决:
第一种,显式指定函数名,通过::标记test_no_mark.py::test_func1
第二种,使用模糊匹配,使用-k选项标识。pytest -k func1 test_no_mark.py
第三种,使用 pytest.mark在函数上进行标记
给用例打标签
注册标签名,通过.ini配置文件,格式如下:
[pytest]
markers =
do: do
undo: undo
在用例上打标记
例子:
1 2 3 4 5 6 7 8 import pytest @pytest.mark.do deftest01(): print('test01') @pytest.mark.undo def test02(): print('test02')
创建pytest.ini文件
1 2 3 4 [test] markers = do:do undo:do
pytest函数 函数级别的测试用例必须test_开头:如下test_tc01,test_tc02两个测试用例
1 2 3 4 5 6 7 8 9 import pytest def test_tc01 (): assert 1 +1 ==2 def test_tc02 (): assert 1 +1 ==3 if __name__ == '__main__' : pytest.main(["test_func01.py" ])
工作一般以类位单元,一个模块一个类,登录类,订单类,购物类
类级别的测试l类必须以Test开头,并且类李不能有init方法,类里面的函数都是test_开头
封装好函数和类就行,其他的交给框架,设置好,框架帮你自动组织怎么运行
封装为了分层,后面更好维护,代码结构整洁。
1 2 3 4 5 6 7 8 9 10 11 12 import pytest class Test_login (): def test_login01 (self ): print ("---test_login01----" ) assert 1 + 1 == 2 def test_login02 (self ): print ("---test_login02----" ) assert 1 + 1 == 3 if __name__ == '__main__' : pytest.main(["test_func01.py" ,"-s" ])
自动化测试里面的环境初始化与清除
环境初始化目的:清空测试环境的垃圾数据,前置条件
需不需要分层:需要。比如:课程模块:课程模块的初始化需要
1:删除所有的课程
2:新增我们的一些课程(这个给修改/查询/删除接口使用)模块级别的(大的课程模块第一件事就是删除以前的课程)
干掉数据后假如需要删除课程,这个接口需要单独的fixture的初始化,增加课程才能删除,其他的接口不需要这个fixture初始化,)
分层:模块层次的初始化,某个接口也需要初始化——框架的分层
条件初始化要和接口挂钩,接口该怎么就要怎么设计
环境初始化和清除,
一头一尾,两个不同概念,(环境的初始化也可以是清除数据)一个接口可以多个级别的fixture,可以
分布式:
1:并行执行 2:分布式
优化运行时间:分布式,(搭建环境麻烦)
什么是环境初始化:
做这个用例之前想要做个操作,初始化动作,比如登录,首先需要连上这个项目(要先能ping通),环境初始化–比如课程新增需要数据全部清空,也是环境初始化。
功能测试:保证测试环境数据和跑什么系统的,或者后台有什么进程执行,或者项目里面测试这功能,功能里面有没有垃圾数据要清除 做个初始化
unittest:最基础的框架,python自带(环境初始化和数据清除用setup和teardown)
jemeter:也有环境清除和初始化
不管做什么测试比如(功能,自动化,性能)都要对当前测试环境初始化,做完后要垃圾数据进行回收(特别是自动化,不然很多用例明明是对的会失败)每次做一个场景,模块的时候,看看模块有没有需要前置的或者环境清除的步骤(基本操作流程)。
pytest是unittest的升级版,对环境清除操作非常灵活(分层分级)
pytest:fixture操作类进行环境初始化 @fixture这样的一个装饰器
pytest的fixture操作
环境初始化与清除
pytest提供的fixture实现unitest中的setup/teardown功能,可以在每次执行case之前初始化数据
不同的是,fixture可以只在执行某几个特定case前运行,只需要在运行case前调用即可,比setup/teardown使用灵活
pytest的初始化和清除可以类里面写个setup_class方法做,以类为单元,模块,包,方法为单元都可以,也可以用fixture来做
pytest前置和后置条件(环境初始化与清除)
环境初始化
1:清除以前的数据
2:测试的时候不是每个接口都要执行,可以定制化执行,固定执行某些接口,先执行删除用例,但是数据已经被清除了,无法删除,修改–需要新增一批测试数据,所以这时候需要环境初始化和清除的想法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import pytest class Test_login (): def setup_class (self ): print ("执行测试类之前,我需要执行操作" ) def test_login01 (self ): print ("---test_login01----" ) assert 1 + 1 == 2 def test_login02 (self ): print ("---test_login02----" ) assert 1 + 1 == 3 def teardown (self ): print ("------该测试类的环境清除-----" ) if __name__ == '__main__' : pytest.main(["test_func01.py" ,"-s" ])
pyets种有四种级别的setup和teardown
1:setup_module和teardown_module,在整个测试用例所在的文件中所在的文件中所有的方法运行前和运行后运行,只运行一次—模块的 2:setup_class和teardown_class,在整个测试文件中的一个class中所有的用例的签后运行 ——class类 3:setup_method和teardown_method,在class内的每个方法运行前后运行 ————-方法的 4:setup_function和teardown_function,在非class下属的每个测试方法的前后运行 ——函数的分层分级(不同级别有不同方法)
pytest参数 在pytest 中,也可以使用参数化测试,即每组参数都独立执行一次测试。
使用的工具就是 pytest.mark.parametrize(argnames,argvalues)。
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 实例: import pytestdata1 =['123' ,'456' ] @pytest.mark.parametrize('pwd' ,data1 ) def test1 (pwd ): print (pwd) data2 = [ (1 ,2 ,3 ) (4 ,5 ,6 ) ] @pytest.mark.parametrize('a,b,c' ,data2 ) def test2 (a,b,c ): print (a,b,c) data3 = ( { 'user' :1 , 'pwd' :2 }, { 'age' :3 , 'email' :'4@qq.com' } ) @pytest.mark.parametrize('pwd' ,data3 ) def test3 (dic ): print (dic) data4=[ pytest.param(1 ,2 ,3 ,id ="(a+b):pass" ), pytest.param(4 ,5 ,10 ,id ="(a+b):fail" ) def add (a,b ) return a + b class TestParametrize (object ): @pytest.mark.parametrize('a,b,expect' ,data_1 ) def test_parametrize_l (self,a,b, expect ): assert add(a,b) == expect
读取文件 Selenium读取csV文件
CSV文件就是逗号分隔的文本文件。
使用python的csv模块来处理csv文件
结合pytest的参数化处理方式来,实现ddt
实例
创建CSV文件,py文件如下:
1 2 3 wqaq,sced,cxx,sdss 123,456,789,000 qwe,qaz,qsx,qdv
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import pytestimport csvdef get_data (): with open ('test_csv.csv' ) as f: lst = csv.reader(f) my_data = [] for row in lst: my_data.extend(row) return my_data @pytest.mark.parametrize('name' ,get_data( ) ) def test01 (name ): print (name) if __name__ == '__main__' : pytest.main(['-sv' ,'test_csv.py' ])
Selenium读取json文件
使用python的json模块来处理json文件
结合pytest的参数化处理方式来,实现ddt
实例
1 2 3 { "key" : [ "Tom" , "Kite" , "Jod" ] }
1 2 3 4 5 6 7 8 9 10 11 12 13 import pytestimport jsondef get_data (): with open ('test_json.json' ) as f: lst = [] data = json.load(f) lst.extend(data['key' ]) return lst @pytest.mark.parametrize('name' ,get_data( ) ) def test01 (name ): print (name) if __name__ == '__main__' : pytest.main(['-sv' ,'test_json.py' ])
Selenium读取Excel文件
安装xlrd模块
使用xlrd模块来处理excel文件
结合pytest的参数化处理方式来,实现ddt
实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import pytestimport xlrddef get_data (): filename = 'test_excel.xls' wb = xlrd.open_workbook(filename) sheet = wb.sheet_by_index(0 ) rows = sheet.nrows cols = sheet.ncols lst = [] for row in range (rows): for col in range (cols): cell_data = sheet.cell_value(row, col) lst.append(cell_data) return lst @pytest.mark.parametrize('name' , get_data( ) ) def test1 (name ): print (name) if __name__ == '__main__' : pytest.main(['-sv' ,'test_excel.py' ])
Selenium读取MySql数据库文件
安装mysqlclient模块
获得数据库连接
查询数据
实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import MySQLdbimport pytestconn = MySQLdb.connect( host="192.168.0.104" , user="root" , password="0000" , database="mydb1" , charset="utf8" ) def get_data (): query_sql = 'select * from user' lst = [] try : cursor = conn.cursor() cursor.execute(query_sql) r = cursor.fetchall() for x in r: u = (x[0 ],x[1 ],x[2 ]) lst.append(u) return lst finally : cursor.close() conn.close() @pytest.mark.parametrize('username,birthday,sex' , get_data( ) ) def testl (username,birthday,sex ): print (username,birthday,sex) if __name__ == '__main__' : pytest.main(['-sv' ,'test_mysql.py' ])
Selenium ddt(用处不多)
安装ddt模块
使用@ddt、@data、@unpack、@file_data加载数据
实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 import osfrom ddt import ddt,data,unpack,file_dataimport unittestdef get_data (): testdata = [{'name' : 'tom' ,'age' : 20 },{'name' : 'kite' , 'age' : 30 }] return testdata @ddt class MyTestCase (unittest.TestCase): @data(1 , 2 , 3 ) def test1 (self, value ): print (value) @data((1 ,2 ,3 ),(4 ,5 ,6 ) ) def test2 (self, value ): print (value) @data((1 ,2 ,3 ),(4 ,5 ,6 ) ) @unpack def test3 (self,value1,value2,value3 ): print (value1,value2,value3) @data([{'name' : 'tom' ,'age' : 20 },{'name' : 'kite' ,'age' : 30 }] ) def test4 (self,value ): print (value) @data({'name' : 'tom' ,'age' : '20' },{'name' : 'kite' ,'age' : '30' } ) def test5 (self,value ): print (value) @data({'name' : 'tom' , 'age' : '20' }, {'name' : 'kite' , 'age' : '30' } ) @unpack def test6 (self,name,age ): print (name,age) testdata = [{'name' : 'tom' ,'age' : 20 },{'name' : 'kite' ,'age' : 30 }] @data(get_data( ) ) def test7 (self,value ): print (value) @file_data(os.getcwd( )+'/test_json.json' ) def test8 (self, value2 ): print (value2) if __name__ == '__main__' : unittest.main()
fixture unittest setup 和 teardown 简介
学过 unittest 的都知道里面用前置和后置setup和teardown 非常好用。
在每次用例开始前和结束后都去执行一次。
当然还有更高级一点的 setupClass 和 teardownClass,需配合 @classmethod 装饰器一起使用。
在做 selenium 自动化的时候,它的效率尤为突出,可以只启动一次浏览器执行多个用例。
模块级(setup_module/teardown_module)开始于模块始末,全局的
函数级(setup_function/teardown_function)只对函数用例生效(不在类中)
类级(setup_class/teardown_class)只在类中前后运行一次(在类中)
方法级(setup_method/teardown_method)开始于方法始末(在类中)
类里面的(setup/teardown)运行在调用方法的前后
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 import pytest@pytest.fixture() def statr1_func (): print ("------初始化操作1------" ) @pytest.fixture() def statr2_func (): print ("------初始化操作2------" ) def test_001 (statr1_func ): print ("-----test01------" ) def test_002 (statr2_func ): print ("-----test02 ------" ) def test_003 (statr2_func,statr1_func ): print ("-----test03 ------" ) if __name__ == '__main__' : pytest.main(["test_pytest.py" ,"-s" ])
类级别的初始化class,可以使用setup做初始化,也可以使用fixture做初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import pytest @pytest.fixture(scope="class" ) def statr1_func (): print ("------初始化操作1------" ) class Test_00 : def test_001 (self,statr1_func ): print ("-----test01------" ) def test_002 (self,statr1_func ): print ("-----test02 ------" ) if __name__ == '__main__' : pytest.main(["test_pytest01.py" ,"-s" ])
类级别初始化fixture,虽然test_001和test_002都调用了statr1_func这个类级别的初始化函数,但是执行类测试用例的时候只执行statr1_func初始函数一次 多个类都可以调用statr1_func这个类级别的初始化方法,调用的时候最好放在类里的第一个函数,后面的函数可以不传(因为对应的是类级别的初始化)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import pytest @pytest.fixture(scope="class" ) def statr1_func (): print ("------初始化操作1------" ) def test_003 (statr1_func ): print ("-----test03------" ) class Test_00 : def test_001 (self,statr1_func ): print ("-----test01------" ) def test_002 (self,statr1_func ): print ("-----test02 ------" ) if __name__ == '__main__' : pytest.main(["test_pytest01.py" ,"-s" ])
初始化方法statr1_func定义成class类级别的,函数级别的测试测试用例test__003调用初始化函数会执行一次, class类级别的测试用例Test_00调用初始化函数会执行一次(一共执行两次) 看级别的,整个模块的级别的化最好用module,否则有问题,fixture可以做return,会有返回值的,对应级别来做, 执行结果: test_pytest01.py ———初始化操作1——— ———test03——— ———初始化操作1——— ———test01——— ———test02 ———
类级别初始化实际代码:初始化操作是登录操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import pytestfrom lib.api_lib.lesson import Lessonfrom lib.api_lib.lesson import Loginfrom tools.execlMethod import get_excelDataimport jsonimport os @pytest.fixture(scope="class" ) def start_func (): global sessionid sessionid = Login().login('{"username":"auto","password":"sdfsdfsdf"}' ) class Test_lesson : @pytest.mark.parametrize("inData,repsData" , get_excelData('2-课程模块' , 2 , 26 ) ) def test_lesson_add (self,start_func,inData,repsData ): reps=Lesson(sessionid).lesson_add(inData) print (reps) assert reps["retcode" ]==json.loads(repsData)["retcode" ] if __name__ == '__main__' : pytest.main(["test_lesson01.py" , "-s" , "--alluredir" , "../report/tmp" ]) os.system("allure serve ../report/tmp" )
模块级别的初始化mudule,不管是类还是方法@pytest.fixture(scope=“module”)模块(module)级别的初始化,(整个模块所有的类所有的东西要做一步操作,可以使用module这个模式)只在模块运行前面只做一次,后面不做了,哪怕多调用也没用,一个模块里面有test_003函数测试用例,也有classTest_00类级别的测试用例,定义一个模块级别的初始化函数statr1_func函数里面调用初始化方法def test_003(statr1_func):
和类里面的方法调用初始化方法
test_001(self,statr1_func):,test_001(self,statr1_func):
整个模块执行的时候初始化函数都只执行一次(不管你这个模块里面调用多少次)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import pytest @pytest.fixture(scope="module" ) def statr1_func (): print ("------初始化操作1------" ) def test_003 (statr1_func ): print ("-----test03------" ) class Test_00 : def test_001 (self,statr1_func ): print ("-----test01------" ) def test_001 (self,statr1_func ): print ("-----test02 ------" ) if __name__ == '__main__' : pytest.main(["test_pytest01.py" ,"-s" ])
1 2 3 4 5 执行结果:test_pytest01.py ------初始化操作1 ------ -----test03------ .-----test01------ .-----test02 ------
在这个模块下面所有的都会调用(包级别的,包里面运行前做个环境清除)需要在testcase文件夹里面创建一个conftest.py模块,这个固定名称,pytest自动识别这个名称
testcase里面:新增课程前面需要登录,增加课程前面需要清除数据,需要2个级别的初始化,1:登录 2:整个环境的清除test_case(测试用例文件夹)创建一个:conftest.py文件 里面写包级别的初始化conftest.py文件里也能写类级别和模块级别的初始化,而且不需要调用,这个conftest.py模块是pytest自动识别导入的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 test_case conftest.py import pytest @pytest.fixture(scope="session" ,autouse=True ) def start_demo (request ): print ("我是整个包的初始化" ) def fin (): print ('---测试完成,包的数据清除---' ) request.addfinalizer(fin)
#session的级别,包里面有很多模块,很多模块需要对整个包进行初始化在conftest.py里面做模块的数据初始化和清除(conftest.py只对当前包有用)
两种调用初始化和清除函数的方式+初始化清除函数的返回值的使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import pytest@pytest.fixture() def befor_func (): print ('xxxxxxxxxxxxx测试用例的初始化xxxxxxxxxxxxxxxx' ) yield 10 print ('zzzzzzzzzzzzzzzzzz测试用例的清除zzzzzzzzzzzzzz' ) def test_001 (befor_func ): print ("测试用例001" ) res=befor_func print (res) @pytest.mark.usefixtures('befor_func' ) def test_002 (): print ("测试用例002" ) if __name__ == '__main__' : pytest.main(["test1.py" ,'-s' ])
pytest前置条件+后置条件的两种写法
1:使用yield关键字来是实现 推荐使用这种,因为yield关键字能返回函数的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import pytest@pytest.fixture() def befor_func (): print ('xxxxxxxxxxxxx测试用例的初始化xxxxxxxxxxxxxxxx' ) yield 10 print ('zzzzzzzzzzzzzzzzzz测试用例的清除zzzzzzzzzzzzzz' ) def test_001 (befor_func ): print ("测试用例001" ) res=befor_func print (res) if __name__ == '__main__' : pytest.main(["test1.py" ,'-s' ])
2:使用finc()函数来实现 这种就不能返回返回值了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import pytest@pytest.fixture() def befor_func (request ): print ('xxxxxxxxxxxxx测试用例的初始化xxxxxxxxxxxxxxxx' ) def fin (): print ('zzzzzzzzzzzz测试用例的清除zzzzzzzzzzz' ) request.addfinalizer(fin) def test_001 (befor_func ): print ("测试用例001" ) if __name__ == '__main__' : pytest.main(["test1.py" ,'-s' ])
16:pytest数据驱动(参数化) pytest数据驱动的意义:
参数化(登录用例4条,每一个账号密码都不同,使用框架把4个用例全部执行完,不需要for循环遍历执行,采用数据驱动方案来做)
pytest内置装饰器@pytest.mark.parametrize可以让测试数据参数化,把测试数据单独管理,类似ddt数据驱动的作用,方便代码和测试数据分离
@pytest.mark.parametrize(“a”,[1,2,3]): 参数化传一组参数
@pytest.mark.parametrize(“a,b”, [(1,2),(3,4),(5,6)]) 参数化传多组参数
登录账户密码(name和psw不同的用例组合,一个接口几十个用例怎么做——几十组数据——传的参数不同(什么请求方式和各种都一样)可以把name和psw分别采取多组数据进行参数化,数据分离,一个接口跑4次,每次用不同的参数)。
1 2 3 4 5 6 7 8 9 10 import pytestclass Test_login (): def setup_class (self ): print ("执行测试类之前,我需要执行操作" ) @pytest.mark.parametrize("a" ,[1 ,2 ,3 ] ) def test_login01 (self,a ):
1 2 3 4 5 6 7 8 9 10 11 12 print ("---test_login01----" ) assert 1 + 1 == a @pytest.mark.parametrize("a,b" , [(1 ,2 ),(3 ,4 ),(5 ,6 )] ) \ def test_login02 (self,a,b ): print ("---test_login02----" ) assert a + 1 == b def teardown_class (self ): print ("------该测试类的环境清除-----" ) if __name__ == '__main__' : pytest.main(["test_func01.py" ,"-s" ])
fixture的自动应用autouse
平常写自动化用例会写一些前置的fixture操作,用例需要用到就直接传该函数的参数名称就行了。当用例很多的时候,每次都传这个参数,会比较麻烦。 fixture里面有个参数autouse,默认是Fasle没开启的,可以设置为True开启自动使用fixture功能,这样用例就不用每次都去传参了
设置autouse=True autouse设置为True,自动调用fixture功能 start设置scope为module级别,在当前.py用例模块只执行一次,autouse=True自动使用[图片]open_home设置scope为function级别, 每个用例前都调用一次,自动使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import pytest@pytest.fixture(scope="module" ,autouse=True ) def start (request ): print ("\n----开始执行module------" ) print ('module : %s' % request.module.__name__) print ('------启动浏览器-------' ) yield print ("------结束测试 end!----------" ) @pytest.fixture(scope="function" ,autouse=True ) def open_home (request ): print ("function:%s \n--回到首页--" % request.function.__name__) def test_01 (): print ('----用例01-----' ) def test_02 (): print ('----用例02-----' ) if __name__ == '__main__' : pytest.main(["-s" ,"autouse.py" ])
1 2 3 4 5 6 7 8 9 10 ----开始执行module------ module : autouse ------启动浏览器------- function:test_01 --回到首页-- .----用例01----- function:test_02 --回到首页-- .----用例02----- ------结束测试 end!----------
allure 部署使用 Pytest作为一个高扩展性、功能强大的自动化测试框架,自身的测试结果是较为简单的,如果想要一份完整测试报告 需要其他插件的支持。如果你对测试报告要求没那么高,你可以使用 pytest-html 插件,基本覆盖了测试报告的常规内容。
注意:allure-pytest 从1.7之后已弃用,从2.0版本开始迁移至 allure-python 项目(即使用allure2),另外要运行allure命令行也需要Java的支持。
Allure提供了一个清晰的全局,涵盖了所涵盖的功能,缺陷聚集的位置,执行时间表,以及许多其他方便的事情。
独特的模块化和可扩展性,确保你能够进行适当的微调,以使更适合你自己。
官方文档:Allure Framework
https://docs.qameta.io/
安装: (1)allure-pytest插件:
1 2 pip install -U allure-pytest source ~/.bash_profile
这将安装allure-pytest和allure-python-commons程序包,以生成与allure2兼容的报告数据。
(2)allure工具:
官方下载地址:https://github.com/allure-framework/allure2/releases
解压软件包(建议直接放到Python文件夹下),然后添加bin目录到环境变量中,最后使用 allure —version 验证是否安装成功。
一:pytest自带的报告框架 pytest-html
二:allure环境搭建(allure是报告库不是python专属的,很全面的框架)-allure报告漂亮
1:下载allure.zip(压缩包) 2:解压allure.zip到一个文件目录 3:将allure-2.13.3\bin路径添加到环境变量path 4:pip install allure-pytest ——-allure报告本身不是很漂亮,通过allure-pytest这个库可以定制化报告,让报告变得很漂亮 5:验证(cmd输入allure)
三:allure和pytest联合执行生成报告:运行两条语句
1:执行pytest单元测试,生成的allure报告需要的数据存在/tmp目录
pytest -sq —alluredir=…/report/tmp #pytest把allure报告的生成的中间文件放到一个临时文件里(pytets生成报告,需要数据,所以先把数据存起来)
#所有的报告需要数据支持的,数据来源pytest框架本身,结果数据存到一个文件,存在…/report/tmp文件夹#tmp临时文件,一般json格式
2:执行命令,生成测试报告
allure generate …/report/tmp -o …/report/report -clean #allure指令生成对应报告
描述
使用方法
参数值
参数值
@allure.epic()
epic描述
敏捷里面的概念,定义史诗,往下是feature
@allure.feature()
模块名称
功能点的描述,往下是story
@allure.story()
用户故事
用户故事,往下是title
@allure.title(用例的标题)
用例的标题
重命名html报告名称
@allure.testcase()
测试用例的链接地址
对应功能测试用例系统里面的case
@allure.issue()
缺陷
对应缺陷管理系统里面的链接
@allure.description
用例描述
测试用例的描述
@allure.step()
操作步骤
测试用例的步骤
@allure.severity()
用例等级
blocker, critical, normal, minor, trivial
@allure.link()
链接
定义一个链接,在测试报告展现
@allure.attachment()
附件
报告添加附件
allure模拟代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 import pytest import os class Test_login (): def setup_class (self ): print ("执行测试类之前,我需要执行操作" ) @pytest.mark.parametrize("a" ,[1 ,2 ,3 ] ) def test_login01 (self,a ): print ("---test_login01----" ) assert 1 + 1 == a @pytest.mark.parametrize("a,b" , [(1 ,2 ),(3 ,4 ),(5 ,6 )] ) def test_login02 (self,a,b ): print ("---test_login02----" ) assert a + 1 == b def teardown_class (self ): print ("------该测试类的环境清除-----" ) if __name__ == '__main__' : pytest.main(["test_func01.py" ,"-s" ,"--alluredir" ,"../report/tmp" ]) os.system("allure generate ../report/tmp -o ../report/report --clean" )
allure报告的优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 import pytest import os import allure @allure.feature("登录模块" ) class Test_login (): def setup_class (self ): print ("执行测试类之前,我需要执行操作" ) @allure.story("登录login01" ) @allure.title("login01" ) @pytest.mark.parametrize("a" ,[1 ,2 ,3 ] ) def test_login01 (self,a ): print ("---test_login01----" ) assert 1 + 1 == a @allure.story("登录login02" ) @allure.title("login02" ) @pytest.mark.parametrize("a,b" , [(1 ,2 ),(3 ,4 ),(5 ,6 )] ) def test_login02 (self,a,b ): print ("---test_login02----" ) assert a + 1 == b def teardown_class (self ): print ("------该测试类的环境清除-----" ) @allure.feature("购物模块" ) class Test_Shopping (): @allure.story("shopping" ) @allure.title("shopping01" ) @pytest.mark.parametrize("a,b" , [(1 , 2 ), (3 , 4 ), (5 , 6 )] ) def test_shopping (self, a, b ): print ("---test_login02----" ) assert a + 1 == b if __name__ == '__main__' : pytest.main(["test_func01.py" ,"-s" ,"--alluredir" ,"../report/tmp" ]) os.system("allure generate ../report/tmp -o ../report/report --clean" )
其他知识点 测试用例一般写在excel表格文件里面,数据分离(维护好excel就行)
pytest–从头到尾到报告执行发邮件
字典是一种存储类型,json是一种格式(完全不同)
pytest参数解析
1 2 3 4 5 6 7 8 9 10 11 12 pytest.main(['test_boss.py' ,'-s' ,'-k test_modify_psw' ,'--alluredir=tmp/my_allure_results' ]) test_boss.py 指定测试用例文件, -s 显示print 语句 -k test_modify_psw 指定某个测试用例 -n 表示用两个进程启动测试脚本 生成报告缓存文件 --alluredir=tmp/my_allure_results os.system('allure serve tmp/my_allure_results' ) 打开测试报告,命令行需要python 的os模块调用
pytest的初始化和清除:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 import pytest @pytest.fixture(scope='module' ) def before_test (): print ("启动被测app" ) print ('连接appium服务' ) yield after_test() def after_test (): print ('关闭被测app' ) print ('断开appium服务' ) @pytest.mark.usefixtures('before_test' ) @pytest.mark.parametrize('psw' ,['boss123' ,'boss456' ] ) def test_app (psw ): print ('测试boss app' ) print (f'登录测试账号{psw} ' ) if __name__ == '__main__' : pytest.main(['pytest_ywt.py' ,'-s' ])
pytest之:不只是测试函数test_app能参数化,初始化函数before_test也能参数化。
重点:
测试用例的参数化+初始化清除函数的参数化
初始化清除函数的参数化能够实现appium的多终端测试
初始化清除函数的参数化,方法很多种:
before_test初始化函数注入参数,因为print(f’连接appium服务{port}’)里面port需要变化的,
@pytest.fixture(scope=‘module’,params=[(4723,),(4727,)]) :初始化清除函数的参数化
始化函数装饰器里面加params参数传参,port=request.param[0] 来调用params里的参数
#初始化清除函数的参数化:只传单个参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import pytest@pytest.fixture(scope=‘module’,params=[(4723 , ),(4727 , )] ) def before_test (request ):port=request.param[0 ] print (“启动被测app”)print (f’连接appium服务{port}') yield #后面写清除动作, after_test() #request是pytest的对象,我们在用对象里面的方法的时候pycham不会自动帮我们取显示名字, #它也不知道request里面到底什么内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 def after_test (): print ('关闭被测app' ) print ('断开appium服务' ) @pytest.mark.usefixtures('before_test' ) @pytest.mark.parametrize('psw' ,['boss123' ,'boss456' ] ) def test_app (psw ): print ('测试boss app' ) print (f'登录测试账号{psw} ' ) if __name__ == '__main__' : pytest.main(['pytest_ywt.py' ,'-s' ]) import pytest
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @pytest.fixture(scope='module' ,params=[(4723 ,'xiaomi' ),(4727 ,'meizu' )]) def before_testquest ): port=request.param[0 ] device=request.param[1 ] print (f"在{device} 启动被测app" ) print (f'连接appium服务{port} ' ) yield after_test() ```cpp def after_test (): print ('关闭被测app' ) print ('断开appium服务' ) @pytest.mark.usefixtures('before_test' ) @pytest.mark.parametrize('psw' ,['boss123' ,'boss456' ] ) def test_app (psw ): print ('测试boss app' ) print (f'登录测试账号{psw} ' ) if __name__ == '__main__' : pytest.main(['pytest_ywt.py' ,'-s' ])
pytest框架执行代码也能在cmd里面直接输入命令执行 xxx\test_case> pytest -s
在test_case这个目录执行会运行test_case文件里面所有的测试文件(test开头的测试用例)
分布式 设置用例每个模块独立,有什么前置做到模块里面,比如测试10个模块,用相关联来做,不能做分布式(并发执行)
每个模块独立还能定制执行那个模块,关联性太强做不到
最好做到每个接口都独立化(前置条件做好)不要做太大关联性的接口
每一层都能做环境清除和定制化(包,模块。类,函数)分层,为后面mark(定点执行哪些用例)和分布式打基础
分布式:必须做到用例的隔离(低耦合,高内聚),用例走串行风险很大,很难维护
3000个请求,全部独立化,然后分布式来做(效率提高几倍–几十倍)
26:分布式的实现 分布式的核心点:封装设计:相互独立,登录和课程相互独立,至少模块为单元要相互独立,封装相互独立,接口用例之间最好也相互独立 才能进行分布式
一:pytest分布式环境搭建和理论介绍:
第一步:安装一个库 pip install pytest-xdist 分布式运行插件,可以做分布式(这个库有两种运行方式)
运行方式:
1:串行,顺序运行,串行运行,从头到尾
2:并行:pytest-xdist做分布式有两种,一种多核,一种多台机器
一:多核:单机多核来做(同时跑) 使用-n参数
电脑多核有假有真:超线程技术(8内核搞成16核),真8核假8核—
cpu个数:硬件,几个cpu槽,i9900.i710–一般电脑就算一个cpu,单cpu,服务器可能有多个cpu
核数: 电脑的核数,
逻辑核数:逻辑核数可以虚拟化,8核可以变成16核(超线程技术)
多核的话xdist本身的多核的话一般用逻辑核数来做的
二:多机(可以使用虚拟机)—需要搭环境,多台机器 很麻烦,装环境,下库
二:测试用例比较多怎么办:分布式 两种情况
1:量大:多机 (需要文件报告收集还需要搭环境,做起来比较麻烦)
2:单机多核 很简单,加-n 参数就行 (做ui和需要一些时间等待的时候时间优化特别明显)
串行运行:本身是线程去跑的,python就一个进程,里面很多线程,
走进程的话需要多台机器来做,分量, 用例设计不好会有大问题,数据不对(用例一定要独立化)
并行和多机:用例一定要设计好,不然数据容易出错,逻辑独立(不能有任何关联,不能有前后关系) 数据和代码封装时候独立化
三:分布式运行代码
#验证单机多核分布式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import pytest import time def test_01 (): time.sleep(3 ) print ("-----test01-----" ) def test_02 (): time.sleep(3 ) print ("-----test01-----" ) if __name__ == '__main__' : pytest.main(["test_xdist.py" , "-sq" ,"-n" ,"8" ])
串行运行本身按照线程去跑的,python本身就一个进程,里面很多线程 走进程的话,多台机器做比较合适—分量, 用例一定要设计好,不然数据容易出错,逻辑独立(不能有任何关联,不能有前后关系)——数据和代码封装时候独立化
pytest cmd执行多个模块用例:pytest test_xdist.py test_login.py -sq :运行两个.py文件(写多个运行多个) pytest cmd执行多个包的用例:pytest test_xdist test_login -sq :运行test_xdist包和test_login 包 还可以运行不同模块的两个包,加包路径 case/test_xdist.py case2/test2_xdist.py
pytest的用例定制化执行 mark标签
所有的接口不需要全部都跑(冒烟,定制化执行某些指定的业务,) “-m”,“test_lesson_add”
一:pytest框架mark标签 标记非常丰富 mark标签
mark标签:对于pytest,我们可以再每一个模块,每一个类,每一个方法和用例前面都加上mark,
那样我们在pytest运行的时候就可以只运行带有该mark标签的模块,类,用例
这样的话可以方便我们选择执行自动化时,是执行全部用例,某个模块用例,
某个流程用例,某个单独用例,总之就是可以某个单独的标签下所有用例
mark可以标记不同层次的东西(类,函数,方法都可以标记)文件不用标记(本身就可以定制化执行)
@pytest.mark.lessson_moudle 给测试类贴个标签,标签名字叫lessson_moudle标识课程模块,
各个函数,类都可以贴上标签(类似别称),选择某个标签就运行某一个(灵活方便)
什么都不选中照常运行,(全部运行,没有限制)
mark标签pytest运行可能报错,
PytestUnknownMarkWarning报错:是一个标签的mark警告,整个pytest这么写不识别你,但是不会报错,只是警告,
消除警告(增加标签栏,相当于标签的声明)
标签声明写法:teach_sq文件夹里创建一个pytest.ini的文件(pycham需要安装ini插件 file-setting-plugins(搜索ini)社区版似乎不行)
pycham找不到可以离线装
teach_sq pytest.ini——-文件内容如下,相当于pytest的mark标签声明一下
筛选测试用例代码:
1 2 3 4 5 6 7 8 9 10 11 12 import pytest @pytest.mark.zzzzz def test_001 (): print ('test_001' ) def test_002 (): print ('test_001' ) if __name__ == '__main__' : pytest.main(["test1.py" ,'-s' ,'-m' ,'zzzzz' ])
allure报告 1、基本使用
>>> 要使allure侦听器能够在测试执行过程中收集结果,只需添加 —alluredir 选项并提供路径即可存储结果。
1 pytest --alluredir=<directory-with-results>
单文件python例子(项目根目录,运行后生产一些文件):
1 2 3 4 pytest --alluredir ./reports/pytest test01.py 启动: allure serve ./reports
插件
1 pip3 install pytest-dependency
如果你运行后进行了用例更改,那么下次运行可能还是会查看到之前记录,可添加 —clean-alluredir 选项清除之前记录。
1 pytest --alluredir=<directory-with-results> --clean-alluredir
>>> 要在测试完成后查看实际报告,你需要使用allure命令行应用程序从结果生成报告。
(1)在默认浏览器中显示生成的报告
1 2 allure serve <my-allure-results> allure serve output
2)要从现有的Allure结果生成报告,可以使用以下命令:
1 allure generate <directory-with-results>
默认报告将生成到allure-report文件夹,你可以使用 -o 标志更改目标文件夹:
1 allure generate <directory-with-results>
(3)生成报告后,可以在默认系统浏览器中将其打开,只需运行:
1 allure open <directory-with-report>
你也可以找到该目录,使用浏览器打开该目录下index.html。注意:有时打开会找不到数据或者乱码,如果你使用的是pycharm,请在pycharm中右击打开。
(4)如果要删除生成的报告数据,只需运行:
默认情况下,报告命令将在 allure-results 文件夹中查找报告,如果要从其他位置使用报告,则可以使用 -o 选项。
(5)你也可以使用 allure help 命令查看更多帮助。
测试报告,你可以在allure报告中看到所有默认的pytest状态:只有由于一个断言错误而未成功进行的测试将被标记为失败,其他任何异常都将导致测试的状态为坏。
1 2 3 4 5 6 7 8 9 10 #更改测试文件的识别规则 python_files=test_*.py #更改测试方法的命名规则 python_functions=test_* #配置生成测试报告# -m level1只运行level1级别的用例 addopts= --html=./reports/report.html -v --alluredir ./allure_temp markers= level1:高级别的用例 level2:低级别的用例