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(测试用例)的使用
'''
# 1、导包
import unittest
# 2、定义测试类:只需要让类继承unittest.TestCase类,这个类就是测试类
class TestDemo(unittest.TestCase):
# 3、定义测试方法:测试方法中的代码就是真正的用例代码,测试方法名必须以test开头
def test_method1(self):
print('测试方法一')

def test_method2(self):
print('测试方法二')
'''
4、执行测试用例的4种方式
4.1 在类名或者方法名后边右键运行
4.1.1在类名后边右键运行:会执行测试类中所有的测试方法
4.1.2在方法名后边右键运行:只执行当前的测试方法
4.2 使用unittest.main()执行测试用例
'''
# 4.2
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’))

#ClassName:测试类名 MethodName:测试方法名
2) 一次添加整个测试类--->suite .addTest(unittest.makeSuite(测试类名)) #在不同的Python版本中,可能没有提示
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

# 步骤1:导包--->import unittest
import unittest

# 导包:导入其他包含测试类测试方法的Python文件
from hm_02_testcase1 import TestDemo1
from hm_02_testcase2 import TestDemo2

# 步骤2:实例化套件对象---> suite = unittest.TestSuite()
suite = unittest.TestSuite()
'''
步骤3:添加用例方法
3.1 方式1:一次添加一个测试方法--->套件对象.addTest(测试类名('测试方法名'))
3.2 方式2:一次添加整个测试类->套件对象.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(ClassName))
#把指定测试类中的测试方法全部添加到测试套件中
3.3 方式3:一次添加整个测试类--->套件对象.addTest(unittest.makeSuite(测试类名))
#在不同的Python版本中,可能没有提示
'''
# 3.1
suite.addTest(TestDemo1('test_method1'))
suite.addTest(TestDemo1('test_method2'))
suite.addTest(TestDemo2('test_method1'))
suite.addTest(TestDemo2('test_method2'))
# 3.2
suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestDemo1))
suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestDemo2))
# 3.3
suite.addTest(unittest.makeSuite(TestDemo1))
suite.addTest(unittest.makeSuite(TestDemo2))
# 步骤4:实例化执行对象(运行对象)---> runner = unittest.TextTestRunner()
runner = unittest.TextTestRunner()
# 步骤5:执行对象执行套件对象---> runner.run(suite)

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 unittest
from Case.test_03_用例执行顺序 import TestLoginPay
from Case.test_04_断言 import TestLogin03
# unittest.main()

# 添加不同的测试用例
# 实例化一个测试套件
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
#test runner报告生成实现代码
import unittest
import HTMLTestRunnerPlugins
import os
import time
# 获取测试报告的路径进行拼接
report_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:
# 实例化一个 HTMLTestRunner
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 unittest
import ddt
from Common.OperationData import OperationData
from 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’)

  • 步骤3:实例化执行对象并执行套件对象

4、注意事项

  • unittest.TestLoader().discover( ‘用例所在的目录’,’用例代码文件名*.py’ )最终返回的还是套件对象,执行套件对象需要使用TestRunner

5、TestLoader与TestSuite区别

img

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 unittest
class 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 unittest
class 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
#unittest断言方法
# 概念
断言:判断预期和实际结果是否相符合
# 语法
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
#unittest断言实现代码
import unittest
class TestLogin03(unittest.TestCase):
def test_login(self):
expected = 'admin'
result = "admin"
print("登录测试")
self.assertEqual(expected,result, msg='用户名输入错误') # 判断表达式a==b成立,则测试通过

def test_add_to_cart(self):
expected = 12
result = 12
print("加入购物车的界面")
self.assertTrue(expected == result,msg='数量不对') # 判断表达式a==b是否为True,为true通过测试

def test_create_order(self):
expected = '230527'
result = 'tbd230527'
print("创建订单的界面")
self.assertIn(expected,result,msg="订单号错误") # 判断表达式a是否在b里面
# self.assertTrue(expected in 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 unittest
import random
# 为0性别为男,为1性别为女
sex = 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、实现代码

img

七、练习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 unittest
from 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}测试不通过')

img

总结一下

  1. unittest是Python自带的单元测试框架,我们可以用其来作为我们自动化测试框架的用例组织执行框架。
  2. unittest的流程:写好TestCase,然后由TestLoader加载TestCase到TestSuite,然后由TextTestRunner来运行TestSuite,运行的结果保存在TextTestResult中,我们通过命令行或者unittest.main()执行时,main会调用TextTestRunner中的run来执行,或者我们可以直接通过TextTestRunner来执行用例。
  3. 一个class继承unittest.TestCase即是一个TestCase,其中以 test 开头的方法在load时被加载为一个真正的TestCase。
  4. verbosity参数可以控制执行结果的输出,0 是简单报告、1 是一般报告、2 是详细报告。
  5. 可以通过addTest和addTests向suite中添加case或suite,可以用TestLoader的loadTestsFrom__()方法。
  6. setUp()tearDown()setUpClass()以及 tearDownClass()可以在用例执行前布置环境,以及在用例执行后清理环境
  7. 我们可以通过skip,skipIf,skipUnless装饰器跳过某个case,或者用TestCase.skipTest方法。
  8. 参数中加stream,可以将报告输出到文件:可以用TextTestRunner输出txt报告,以及可以用HTMLTestRunner输出html报告。

pytest框架

pytest

  1. 简单灵活,容易上手;支持参数化; 测试用例的skip和xfail 处理;
  2. 能够支持简单的单元测试和复杂的功能测试,还可以用来做 selenium/appium等自动化测试、接口自动化测试 (pytest+requests);
  3. pytest具有很多第三方插件,并且可以自定义扩展, 比较好 用的如 pytest-allure(完美html测试报告生成) pytest-xdist (多CPU分发)等;
  4. 可以很好的和jenkins集成;
  5. 等等

安装

导入相关依赖库

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方式执行)

  1. Pytest –v (最高级别信息—verbose)
  2. 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"]) #我主动运行我的pytest框架(自动调用所有的test测试函数,按照顺序依次运行,test开头的用例自动识别)

工作一般以类位单元,一个模块一个类,登录类,订单类,购物类

类级别的测试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"]) #框架自己调用函数  需要打印对应的信息,需要在列表里面加-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): #类级别的初始化--可选项
#一个项目,先登录,再购物,登录就是购物类的前置条件,可以放在setup_class里面
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 pytest
#列表
data1 =['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)
#id
data4=[
pytest.param(1,2,3,id="(a+b):pass"),# id的值可以自定义,只要方便理解每个用例是干什么的即可
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 pytest
import csv
def 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 pytest
import json
def 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 pytest
import xlrd
def 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 MySQLdb
import pytest
conn = 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 os
from ddt import ddt,data,unpack,file_data
import unittest
def 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(*testdata)
@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 自动化的时候,它的效率尤为突出,可以只启动一次浏览器执行多个用例。
  1. 模块级(setup_module/teardown_module)开始于模块始末,全局的
  2. 函数级(setup_function/teardown_function)只对函数用例生效(不在类中)
  3. 类级(setup_class/teardown_class)只在类中前后运行一次(在类中)
  4. 方法级(setup_method/teardown_method)开始于方法始末(在类中)
  5. 类里面的(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()初始化操作

@pytest.fixture() #标记函数是个初始化操作,标记后需要传给每个函数statr_func这个函数名才会执行初始化操作(函数级别的)

def statr1_func():

#这不是测试函数,一个普通函数,pytest执行用例只能识别test开头的方法和函数,所以pytest.main不会执行(不参加pytest用例)

print("------初始化操作1------")

@pytest.fixture()

def statr2_func():

print("------初始化操作2------")

#fixture:有哪些操作(可以多个初始化可以一起调,需要两个初始化,需要连接,需要登录)

#这种写法很方便,函数需要statr_func1函数做一个初始化操作可以调用statr_func1这个函数,---def test_001(statr1_func):

# 需要其他初始化方法可以选择性调用其他初始化函数,传递函数名就行(灵活选择)----def test_002(statr2_func):

#函数初始化操作需要传递几个函数也可以多个函数名传递--def test_003(statr2_func,statr1_func):

#方便灵活

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") #类级别的初始化函数 scope="class" 就是把这个初始化定义成类级别的
def statr1_func():
print("------初始化操作1------")
class Test_00: #需要执行 Test_00测试类,需要做初始化(可以setup_class)
# def setup_class(self):
# print("类内部的初始化,")#只对类有用,类级别的,类里只做一次(几个类的初始化操作一样这种不适合,需要重复写)
#fixture初始化类就是避免重复代码
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------")
#一个模块里面有函数用例也有类的用例怎么做:(class级别的初始化只对类有用,对函数没用)
def test_003(statr1_func): #测试函数,
print("-----test03------")

class Test_00: #需要执行test00测试类,需要做初始化(可以setup_class)
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 pytest
from lib.api_lib.lesson import Lesson
from lib.api_lib.lesson import Login
from tools.execlMethod import get_excelData
import json
import os

@pytest.fixture(scope="class")   #类级别的初始化函数
def start_func():
global sessionid
sessionid = Login().login('{"username":"auto","password":"sdfsdfsdf"}')

class Test_lesson:
#1:课程新增接口,前置条件登录(封装完一个方法后想办法做数据驱动),课程增加需要通过excel表用例来做
@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------")

#一个模块里面有函数用例也有类的用例怎么做:(class级别的初始化只对类有用,对函数没用)
def test_003(statr1_func):  #测试函数,
print("-----test03------")

class Test_00:
#需要执行test00测试类,需要做初始化(可以setup_class)
# def setup_class(self):
# print("类内部的初始化,")  
# 只对类有用,类级别的,类里只做一次(几个类的初始化操作一样这种不适合,需要重复写)
# fixture初始化类就是避免重复代码

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)
#session级别的处于时候autouse=True默认自动执行
def start_demo(request): #包的开始
print("我是整个包的初始化")
def fin():
#尾部这是包级别的,整个包做完后做个环境数据的清除 包的结束
print('---测试完成,包的数据清除---')
request.addfinalizer(fin) 
#回调,当我整个包运行完了后回调fin这个方法
#fixture的参数autouse: bool = False,---自动执行参数

#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')        
#调用初始化和清除方式二:使用usefixtures放在测试用例前面直接调用初始化清除函数
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                            
#yield后面跟的是测试用例的后置条件,支持用例执行后就执行yield里的内容
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)               
#回调,当我整个包运行完了后回调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 pytest
#[(1,2),(3,4),(5,6)] [1,2,3]
class Test_login():
def setup_class(self):
print("执行测试类之前,我需要执行操作")
@pytest.mark.parametrize("a",[1,2,3])
#("变量名",[1,2,3]),数据需要封装成一个列表,多个数据需要封装成列表嵌套元组----数据驱动
def test_login01(self,a):
#数据驱动,一定要把变量名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__':

#需要打印对应的信息,需要在列表里面加-s
#1:--alluredir ---生成临时文件,测试用例的结果数据放到目录 --alluredir 存放目录
pytest.main(["test_func01.py","-s","--alluredir","../report/tmp"])
#框架自己调用函数
#通过--alluredir把allure需要的数据存到../report/tmp这个路径下面
#../--所在路径的父级别目录是test_case的目录隔壁邻居report文件下tmp,专门放alluer报告生成的需要的数据源
# 2:临时数据没有报告的,allure generate allure才会生成报告 -----allure生成器生成allure报告--generate allure生成器,cmd指令
#需要os模块os.system()调用指令可以在local的cmd里面敲
os.system("allure generate ../report/tmp -o ../report/report --clean")
#os.system("allure generate 报告需要的数据 -o 报告存放目录 --clean")
#-o生成
#allure generate生成报告指令,把../report/tmp 的文件-o生成报告out out一下,生成的报告放在../report/report
#--clean把上次报告清除一下用--clean
        #allure报告生成的是一个服务,(本地服务)和jinkins结合,放在整个里面去集成,放到公共服务器里面

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") # 标题,每个用例带个标题(报告体现在每个测试用例)(一个接口有几个用例,title用例的标签)
@pytest.mark.parametrize("a",[1,2,3])
def test_login01(self,a):
print("---test_login01----")
assert 1 + 1 == a

@allure.story("登录login02") # 二级标签,定制allure报告层级
@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")
#allure报告生成的是一个服务,(本地服务)和jinkins结合,放在整个里面去集成,放到公共服务器里面。

其他知识点
测试用例一般写在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
#假设启动被测app的时候需要去填写配置项信息,每个的端口号不同,多终端需要两个appim server
#这时候setup_module和teardown_module不能传参,搞不定,需要换一种方法做测试用例的初始化和清除,           
#setup_module以模块为作用域,不写module以测试用例(测试函数)为作用域
# def setup_module():   
#测试用例之前执行,原始的setup和teardown有个缺陷,里面不能传参数       
#默认test级别,每个测试用例执行的时候都会执行一次,希望当前某个模块执行的时候只执行一次(不管里面用例执行多少次)       
#setup初始化和tear_down升个级,升级成module模块级别的
# print("启动被测app")
# print('连接appium服务')
# def teardown_module():
# print('关闭被测app')
# print('断开appium服务')
#定义个函数,名字随便取  使用@pytest.fixture装饰器把这个函数装饰成初始化清除函数
@pytest.fixture(scope='module')
#作用域默认test,初始化,加装饰器,初始化清除函数,autouse=True(自动执行)这种方法不建议使用
def before_test():
#初始化函数升级作用域到module模块级别
print("启动被测app")
print('连接appium服务')
yield #后面写清除动作,
after_test()

#清除函数,清除函数并不会直接被初始化函数使用,我们必须放在初始化函数yiled后面才能回被调用
def after_test():
print('关闭被测app')
print('断开appium服务')
#目前一共有两个port,需要测试两个手机,两个多终端,before_test需要装饰器标记
#测试用例的参数化
@pytest.mark.usefixtures('before_test')
#这表示调用某个自定义的初始化函数,括号里面的字符串写被调用函数的名字
@pytest.mark.parametrize('psw',['boss123','boss456'])
def test_app(psw):
#测试用例,可能涉及到其他参数,比如需要一些配置信息,测试用例涉及到参数,                      
#多组参数需要使用装饰器pytest.mark.parametrize(数据驱动),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,)])
#初始化清除函数的参数化params
def before_test(request):
port=request.param[0]               
#param[0],假如注入多个参数一个port和一个data–需要params传元组,
#params=[(4723,100),(4727,200)],一个参数的话不需要写成列表嵌套元素,                                   
#params[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]       
#param[0],假如注入多个参数一个port和一个data,需要params传元组,params=[(4723,100),(4727,200)],     
#一个参数的话不需要写成列表嵌套元素,request.params[0]代表获取元组第一个
device=request.param[1]     
#request.param[1]对应元素里面第二个参数,
print(f"在{device}启动被测app")
print(f'连接appium服务{port}')
yield
#后面写清除动作,
after_test()
#request是pytest的对象,(固定写法:request.param)
          #我们在用对象里面的方法的时候pycham不会自动帮我们去显示名字,它也不知道request里面到底什么内容
```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"])         
#这是串行跑的 6s
pytest.main(["test_xdist.py", "-sq","-n","8"])     
#单机多核并行 加"-n","8" 参数 ,用8个核来跑,5.41s,时间少了
#或者测试用例文件目录下 cmd,输入 pytest test_xdist.py -n 8 也可以 这样cmd里执行看到的结果更直观,
#多核来跑在ui里面时间提升很大,ui里面很多地方需要sleep,等待元素(有等待的的提升比较大)多核跑更快
#有等待的情况用多核跑效果越明显

串行运行本身按照线程去跑的,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)如果要删除生成的报告数据,只需运行:

1
allure report clean

默认情况下,报告命令将在 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:低级别的用例