编写规范: • 测试⽂件以 test_ 开头(以 _test 结尾也可以)! • 测试类以 Test 开头,并且不能带有 __init__ ⽅法! • 测试函数以 test_ 开头
1)创建python文件
def add(x, y): return x + y def test_add(): print("---add1----") assert add(1, 2) == 3 def test_add2(): print("---add2----") assert add(1.2, 3.1) == 42.3
2)控制台运行
pytest test_p02.py
D:javaideaworkSpaces estpythonWebsimple est03>pytest test_p02.py ====================================================================== test session starts ======================================================================= platform win32 -- Python 3.8.1, pytest-6.2.1, py-1.10.0, pluggy-0.13.1 rootdir: D:javaideaworkSpaces estpythonWebsimple est03 plugins: html-3.1.1, metadata-1.11.0, remotedata-0.3.2 collected 2 items test_p02.py .F [100%] ============================================================================ FAILURES ============================================================================ ___________________________________________________________________________ test_add2 ____________________________________________________________________________ def test_add2(): print("---add2----") > assert add(1.2, 3.1) == 42.3 E assert 4.3 == 42.3 E + where 4.3 = add(1.2, 3.1) test_p02.py:11: AssertionError ---------------------------------------------------------------------- Captured stdout call ---------------------------------------------------------------------- ---add2---- ==================================================================== short test summary info ===================================================================== FAILED test_p02.py::test_add2 - assert 4.3 == 42.3 ================================================================== 1 failed, 1 passed in 0.63s ===================================================================
2、参数化
import pytest @pytest.mark.parametrize("x,y",[ (3+3,8), (9-1,8), (2*4,8), (32/4,8) ]) def test_math(x,y): assert x==y
运行 pytest test_p03.py
D:javaideaworkSpaces estpythonWebsimple est03>pytest test_p03.py ====================================================================== test session starts ======================================================================= platform win32 -- Python 3.8.1, pytest-6.2.1, py-1.10.0, pluggy-0.13.1 rootdir: D:javaideaworkSpaces estpythonWebsimple est03 plugins: html-3.1.1, metadata-1.11.0, remotedata-0.3.2 collected 4 items test_p03.py F... [100%] ============================================================================ FAILURES ============================================================================ _________________________________________________________________________ test_math[6-8] _________________________________________________________________________ x = 6, y = 8 @pytest.mark.parametrize("x,y",[ (3+3,8), (9-1,8), (2*4,8), (32/4,8) ]) def test_math(x,y): > assert x==y E assert 6 == 8 test_p03.py:10: AssertionError ==================================================================== short test summary info ===================================================================== FAILED test_p03.py::test_math[6-8] - assert 6 == 8 ================================================================== 1 failed, 3 passed in 0.60s ===================================================================
单个参数,随机数
import pytest import random @pytest.mark.parametrize("x",[(1),(3),(5),(7)]) def test_random(x): ran = random.randrange(1,8) assert x == ran
运行
参数化方式二:
import pytest test_user_data=['zs','123456'] @pytest.fixture(scope="module") def login_r(request): user = request.param+'___00加工00' print(' 登录用户%s'%user) return user #indirect=True,把login_r当做函数来执行 @pytest.mark.parametrize("login_r",test_user_data,indirect=True) def test_login(login_r): a=login_r print('测试用例返回:%s'%a) assert a != ""
执行原理:将参数test_user_data传入方法login_r()进行加工,加工完成后,返回到test_login
结果
参数化--字典格式数据
import pytest test_user_data=[{'user':'zs','pwd':'123456'}, {'user':'ls','pwd':'456'}, {'user':'ww','pwd':'16'}, {'user':'sa','pwd':''} ] @pytest.fixture(scope="module") def login_r(request): user = request.param['user'] pwd = request.param['pwd'] print(' 登录用户:%s,pwd:%s'%(user,pwd)) if pwd: return True else: return False #indirect=True 把login_r当做函数执行 @pytest.mark.parametrize("login_r",test_user_data,indirect=True) def test_login(login_r): a=login_r print('测试用例返回:%s'%a) assert a ,'登录失败,密码为空'
结果
collecting ... 登录用户:zs,pwd:123456 测试用例返回:True test_p02.py ✓ 25% ██▌ 登录用户:ls,pwd:456 测试用例返回:True test_p02.py ✓✓ 50% █████ 登录用户:ww,pwd:16 测试用例返回:True test_p02.py ✓✓✓ 75% █████ ██▌ 登录用户:sa,pwd: 测试用例返回:False ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― test_login[login_r3] ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― login_r = False @pytest.mark.parametrize("login_r",test_user_data,indirect=True) def test_login(login_r): a=login_r print('测试用例返回:%s'%a) > assert a ,'登录失败,密码为空' E AssertionError: 登录失败,密码为空 E assert False test_p02.py:27: AssertionError test_p02.py ⨯ 100% █████ █████ ==================================================================== short test summary info ===================================================================== FAILED test_p02.py::test_login[login_r3] - AssertionError: 登录失败,密码为空 Results (0.60s): 3 passed 1 failed - test_p02.py:23 test_login[login_r3]
参数化---多个字典
import pytest test_user_data=[{'user':'zs','pwd':'123456'}, {'user':'ls','pwd':'456'}, {'user':'ww','pwd':'16'} ] test_user_data2 = [{"q": "中国平安", "count": 3, "page": 1}, {"q": "阿里巴巴", "count": 2, "page": 2}, {"q": "pdd", "count": 3, "page": 1}] @pytest.fixture(scope="module") def login_r(request): #数据加工 user = request.param['user'] pwd = request.param['pwd'] print(' 登录用户:%s,pwd:%s'%(user,pwd)) return request.param @pytest.fixture(scope="module") def query_param(request): q = request.param['q'] count = request.param['count'] page = request.param['page'] print("查询的搜索词:%s" % q) return request.param # 这是pytest的参数化数据驱动,indeirect=True 是把login_r当作函数去执行 # 从下往上执行 # 两个数据进行组合测试有3*3个测试用例执行(test_user_data1的个数*test_user_data2的个数) @pytest.mark.parametrize("query_param",test_user_data2,indirect=True) @pytest.mark.parametrize("login_r",test_user_data,indirect=True) def test_login(login_r,query_param): values = login_r.values() print('用户名 %s ,密码:%s'%(list(values)[0],list(values)[1])) u_values = query_param.values() print('名称:%s,总共:%s页,当前:%s页' %(list(u_values)[0],list(u_values)[1],list(u_values)[2]))
结果
D:javaideaworkSpaces estpythonWebpytest_simple est06>pytest -s test_p03.py Test session starts (platform: win32, Python 3.8.1, pytest 6.2.1, pytest-sugar 0.9.4) rootdir: D:javaideaworkSpaces estpythonWebpytest_simple est06 plugins: assume-2.4.2, html-3.1.1, metadata-1.11.0, ordering-0.6, remotedata-0.3.2, rerunfailures-9.1.1, sugar-0.9.4 collecting ... 登录用户:zs,pwd:123456 查询的搜索词:中国平安 用户名 zs ,密码:123456 名称:中国平安,总共:3页,当前:1页 test_p03.py ✓ 11% █▎ 查询的搜索词:阿里巴巴 用户名 zs ,密码:123456 名称:阿里巴巴,总共:2页,当前:2页 test_p03.py ✓✓ 22% ██▎ 登录用户:ls,pwd:456 用户名 ls ,密码:456 名称:阿里巴巴,总共:2页,当前:2页 test_p03.py ✓✓✓ 33% ███▍ 查询的搜索词:中国平安 用户名 ls ,密码:456 名称:中国平安,总共:3页,当前:1页 test_p03.py ✓✓✓✓ 44% ████▌ 查询的搜索词:pdd 用户名 ls ,密码:456 名称:pdd,总共:3页,当前:1页 test_p03.py ✓✓✓✓✓ 56% █████ ▋ 登录用户:zs,pwd:123456 用户名 zs ,密码:123456 名称:pdd,总共:3页,当前:1页 test_p03.py ✓✓✓✓✓✓ 67% █████ █▋ 登录用户:ww,pwd:16 用户名 ww ,密码:16 名称:pdd,总共:3页,当前:1页 test_p03.py ✓✓✓✓✓✓✓ 78% █████ ██▊ 查询的搜索词:阿里巴巴 用户名 ww ,密码:16 名称:阿里巴巴,总共:2页,当前:2页 test_p03.py ✓✓✓✓✓✓✓✓ 89% █████ ███▉ 查询的搜索词:中国平安 用户名 ww ,密码:16 名称:中国平安,总共:3页,当前:1页 test_p03.py ✓✓✓✓✓✓✓✓✓ 100% █████ █████ Results (0.23s): 9 passed
3、运行进度条
安装 : pip install pytest-sugar
4、assume
pip install assume
assert断言失败之后,后面的断言也不会执行,包括正常的代码
assume即使断言失败,后面的断言还是会继续执行,这有助于我们分析和查看到底一共有哪些断言是失败的,直接用assert更高效
import pytest def test_assume(): pytest.assume(1 == 2) pytest.assume(2 == 2) pytest.assume(3==2)
5、rerunfailures
在web、APP⾃动化测试中, 经常出现超时导致测试失败,所以需要重新运行
pip install pytest-rerunfailures
测试代码
import random def add(x,y): return x+y def test_add(): ran=random.randint(1,10) assert add(1,3) == ran
运行结果
pytest --reruns 8 test_p07.py
设置最大重新运行测试为8。
rerun 5次,总共运行6次,第六次时测试通过,不再运行。
6、Pytest-ordering
使得测试方法按照指定顺序运行
pip install pytest-ordering
代码
import pytest value = 0 @pytest.mark.run(order=2) def test_add(): print("---add [order=2] ---") assert value == 10 @pytest.mark.run(order=1) def test_sub(): print("--sub [order=1]---") global value value = 10 assert value == 10
7、冒泡排序
def bubbleSort(arr): n = len(arr) # 遍历所有数组元素 for i in range(n): for j in range(0, n-i-1): if arr[j] > arr[j+1] : arr[j], arr[j+1] = arr[j+1], arr[j] arr = [64, 34, 25, 12, 22, 11, 90] bubbleSort(arr) print ("排序后的数组:") for i in range(len(arr)): print ("%d" %arr[i])
8、函数级别
代码
import pytest def setup_module(): print('整个模块.py开始') def teardown_module(): print('整个模块.py结束') def setup_function(): print('不在类中的函数前') def teardown_function(): print('不在类中的函数后') def test_no_01(): print('不在类中的方法1') def test_no_02(): print('不在类中的方法2') class TestClass: def setup_class(self) -> None: print('类前面') def teardown_class(self): print('类之后') def setup_method(self): print('方法前') def teardown_method(self): print('方法后') def test_one(self): print('one') def test_two(self): print('two') if __name__ == '__main__': pytest.main(["-s", "test_p01.py"])
结果
模块(setup_module) --》不在类中的方法(setup_function)--》具体方法,不再在中--》类(setup_class)--》类中的方法(setup_method)--》具体方法
9、运行方式
10.pytest-fixture
类似于setup,在一个方法之前运行
在加入购物车、购买之前先登录,scope="function"
实现方式一:直接将fixture的引用写在方法的参数,如 test_shoppingChart(login):
import pytest @pytest.fixture() def login(): print('登录') def test_select(): print('搜索') def test_shoppingChart(login): print('加入购物车') def test_pay(login): print('购买') if __name__ == '__main__': pytest.main(['-s', 'test_p02.py'])
结果
实现方式二:@pytest.mark.usefixtures("login")
import pytest @pytest.fixture() def login(): print('登录') def test_select(): print('搜索') @pytest.mark.usefixtures("login") def test_shoppingChart(): print('加入购物车') @pytest.mark.usefixtures("login") def test_pay(): print('购买')
将公共模块放入共享文件
查找顺序:先在本文件中查找,如果没找到,在从conftest.py中查找
例如:刚才的例子,将login放进conftest.py中,进行数据共享
运行
11、yield
使用场景:在执行方法前要执行依赖的模块,在执行方法后要销毁清除数据,范围模块级别 scope="module"
import pytest @pytest.fixture(scope="module") def openBrower(): print('打开浏览器') yield print('关闭浏览器') def test_search(openBrower): print('搜索:shell') def test_look(openBrower): print('查看浏览器返回结果') def test_searchAagin(openBrower): print('搜索:bash')
结果
如上述代码如果每个方法的调用都通过参数指定,会比较麻烦而且不利于扩展,可以选择
使用fixture中参数autouse=True,替换掉方法参数
import pytest
@pytest.fixture(scope="module",autouse=True)
def openBrower():
print('打开浏览器')
yield
print('关闭浏览器')
def test_search():
print('搜索:shell')
def test_look():
print('查看浏览器返回结果')
def test_searchAagin():
print('搜索:bash')
综合:@pytest.fixture(scope="module", autouse=True),@pytest.mark.usefixtures("login")一起使用
import pytest @pytest.fixture(scope="module",autouse=True) def openBrower(): print('打开浏览器') print('进入网页版淘宝') yield print('关闭浏览器') @pytest.fixture(scope="function") def login(): print('登录') #搜索:不用登陆 def test_search(): print('搜索:包包') #加入购物车之前,先登录 @pytest.mark.usefixtures("login") def test_chart(): print('加入购物车') #下单之前,先登录 @pytest.mark.usefixtures("login") def test_pay(): print('下单')
结果
12、skip与xfail
代码
import pytest import sys import time # scope=function ''' skip ''' def test_soso(login): print('case1: 登录后执行搜索') assert 1 == 1 assert {'name': 'linda', 'age': 18} == {'name': 'linda', 'age': 188} a = 'hello' age = 35 assert a in 'hello world' assert 20 < age < 80 def f(): return 3 @pytest.mark.skip def test_cakan(): print('case2:不登陆,就看是否执行') assert f() == 4 environment = 'android' @pytest.mark.skipif('environment=="android"', reason='android平台没有这个功能') def test_cart_3(login): print('case3,登陆,点击苹果图标') @pytest.mark.skipif(sys.platform =='win32', reason='不在windows下运行') @pytest.mark.skipif(sys.version_info < (3,6), reason='3.6版本行,您需要更高版本') def test_cart(login): print('case3,登陆,点击苹果图标,3.6以下版本无法执行') @pytest.mark.xfail def test_xfail(): print(broken_fixture()) def broken_fixture(): raise Exception("Sorry, it's 中断异常.")
运行
运行时指定平台
代码
import pytest @pytest.mark.webtest def test_send_http(): pass @pytest.mark.apptest def test_devide(): pass @pytest.mark.android def test_search(): pass @pytest.mark.ios def test_add(): pass def test_plus(): pass if __name__ == '__main__': pytest.main()
运行
pytest -s test_p05.py -m "ios"
13、通过文件名、类名、方法名及组合调动部分用例执行
pytest-s -v 文件名::类名::方法名
pytest -k "类名 and 方法名"
14、用例出错时停止
15、多线程并行与分布式执行
16、pytest-html生成测试报告
pip install pytest-html
pytest -v -s --html=report.html -- self-contained-html
17、allure生成测试报告
1)下载
https://github.com/allure-framework/allure2/releases
2)安装
解压到任意⽬录下,把 allure-2.7.0in, 加⼊到环境path⾥⾯
命令⾏下执⾏: pip install allure-pytest
命令⾏下执⾏:allure --version 如果出现版本号就代表安装成功
3)使用
方式一:
在测试期间收集结果
pytest -s -q --alluredir=./result/
从结果生成报告,这是一个启动Tomcat的服务,只生成报告.clean用于覆盖路径
allure generate ./result/ -o ./report/ --clean
打开报告
allure open -h 127.0.0.1 -p 8883 ./report/
方式二
在测试期间收集结果
pytest -s -q --alluredir=./result/
测试完成后查看实际报告,在线看报告
allure serve ./result/
18、在测试报告中增加‘测试功能、子功能/场景、测试步骤、附加信息’等信息
利用@Feature,story,step,@attach
步骤:
1)import allure
2)在功能上加@allure.feature('功能名称')
3)在子功能上加@allure.story('子功能名称')
4)在步骤上加@allure.step('步骤细节)
5)需要附加信息,可以是数据、文本、图片、网页;在要附加的地方加@allure.attach('具体文本信息')
6)如果只测试购物车功能可以限制顾虑:pytest 文件名 --allure_features='购物车功能' --allure_stories='加入购物车'
19、按照重要级别进行一定范围测试
lure.severity("critical") # 优先级,包含blocker, critical, def test_case_19688(para_one, para_two):
if __name__ == '__main__': # 执行,指定执行测试模块_demo1, 测试模块_demo2两个模块,同时指定执行的用例优先级为critical,blocker pytest.main(['--allure_feature=测试功能_demo1', '--allure_stories=测试模块_demo2', '--allure_severities=critical, blocker'])
完整代码
import pytest import allure import logging ''' test_allure_all.py ''' # 测试函数 @allure.step("测试步骤1:字符串相加:{0},{1}") # 测试步骤,可通过format机制自动获取函数参数 def str_add(str1, str2): print("hello",str1,str2) if not isinstance(str1, str): return "%s 不是字符串" % str1 if not isinstance(str2, str): return "%s 不是字符串" % str2 return str1 + str2 @allure.description("测试相加的各种情况") @allure.severity("critical") # 优先级,包含blocker, critical, normal, minor, trivial 几个不同的等级 @allure.feature("测试功能_demo1") # 功能块,feature功能分块时比story大,即同时存在feature和story时,feature为父节点 @allure.story("测试模块_demo2") # 功能块,具有相同feature或story的用例将规整到相同模块下,执行时可用于筛选 # @allure.issue("BUG号:123") # BUG编号,关联标识已有的问题,可为一个url链接地址 @allure.issue("http://www.jira.com/id=19688") # BUG编号,关联标识已有的问题,可为一个url链接地址 # @allure.testcase("用例名:测试字符串相等") # 用例标识,关联标识用例,可为一个url链接地址 @allure.testcase("http://www.testlink.com/id=19688") @pytest.mark.parametrize("para_one, para_two", # 用例参数 [("hello world", "hello world"), # 用例参数的参数化数据 ('4', '54'), ("我不是超人", "我是超人"), ("888", "我是超人")], ids=["letter", # 对应用例参数化数据的用例名 "decimal123", "unicode", "mix"]) def test_case_19688(para_one, para_two): """用例描述:测试字符串相等 :param para_one: 参数1 :param para_two: 参数2 """ logging.info("这是测试的信息,在log中输出") # 获取参数 paras = vars() # 关联的资料信息, 可在报告中记录保存必要的相关信息 allure.attach("用例参数", "{0}".format(paras)) # 调用测试函数 res = str_add(para_one, para_two) # 对必要的测试中间结果数据做备份 allure.attach("str_add返回结果", "{0}".format(res)) # 测试步骤,对必要的测试过程加以说明 with allure.step("测试步骤2,结果校验 {0} == {1}".format(res, para_one+para_two)): allure.attach('<html><head></head><body> 附加网页看效果 </body></html>', '这是错误页的结果信息', allure.attachment_type.HTML) assert res == para_one+para_two, res if __name__ == '__main__': # 执行,指定执行测试模块_demo1, 测试模块_demo2两个模块,同时指定执行的用例优先级为critical,blocker pytest.main(['--allure_feature=测试功能_demo1', '--allure_stories=测试模块_demo2', '--allure_severities=critical, blocker'])
20、前端自动化测试截图
完整代码
import allure from selenium import webdriver import time import pytest ''' test_sele_allure.py ''' @allure.testcase("https://www.baidu.com的搜索功能") @pytest.mark.parametrize('test_data1', ['allure', 'pytest', 'unittest']) def test_steps_demo(test_data1): with allure.step('step one:打开浏览器输入百度网址'): driver = webdriver.Chrome() driver.get('https://www.baidu.com') with allure.step('step two:在搜索栏输入allure,并点击百度一下'): driver.find_element_by_id('kw').send_keys(test_data1) time.sleep(1) driver.find_element_by_id('su').click() time.sleep(1) with allure.step('step three:截图保存到项目中'): driver.save_screenshot("./result/b.png") # f = open('./result/b.png', 'rb').read() allure.attach.file("./result/b.png", attachment_type=allure.attachment_type.PNG) allure.attach('<head></head><body> 首页</body>', 'Attach with HTML type', allure.attachment_type.HTML) with allure.step('step four:关闭浏览器,退出'): driver.quit()
结果
分布式测试插件:https://github.com/pytest-dev/pytest-xdist
https://github.com/linda883/