2024年最全Pytest和Allure测试框架-超详细版+实战
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加戳这里获取
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
import pytest
class Test_Pytest():
@pytest.mark.xfail def test\_one(self): print("test\_one方法执行" ) assert 1==2 def test\_two(self): print("test\_two方法执行" ) assert "o" in "love" def test\_three(self): print("test\_three方法执行" ) assert 3-2==1
if name==“__main__”:
pytest.main([‘-s’,‘test_Pytest.py’])
运行结果如下图:可以看到我们标记的用例确实运行了;因为断言失败所以结果是xfailed,也没有像正常一样显示出错误用例及具体信息。 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190923143947175.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNjEwMTY3,size_16,color_FFFFFF,t_70) 我们把断言改成正确的,再运行一次,结果如下图:尽管我们的用例正常运行通过,但是仍被标记为xpassed,而不是passed。 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190923143953557.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNjEwMTY3,size_16,color_FFFFFF,t_70) ### 4,使用自定义标记mark只执行部分用例 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190923144334345.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNjEwMTY3,size_16,color_FFFFFF,t_70) **1.mark标记** 以下用例,标记test\_send\_http()为webtest
content of test_server.py
import pytest
@pytest.mark.webtest
def test_send_http():
pass # perform some webtest test for your app
def test_something_quick():
pass
def test_another():
pass
class TestClass:
def test_method(self):
pass
if name == “__main__”:
pytest.main([“-s”, “test_server.py”, “-m=webtest”])
只运行用webtest标记的测试,cmd运行的时候,加个-m 参数,指定参数值webtest
pytest -v -m webtest
如果不想执行标记webtest的用例,那就用”not webtest”
pytest -v -m “not webtest”
import pytest @pytest.mark.webtest def test\_send\_http(): pass # perform some webtest test for your app def test\_something\_quick(): pass def test\_another(): pass class TestClass: def test\_method(self): pass if __name__ == "\_\_main\_\_": pytest.main(["-s", "test\_server.py", "-m='not webtest'"])
5,文件名类名方法执行部分用例
2.-v 指定的函数节点id
如果想指定运行某个.py模块下,类里面的一个用例,如:TestClass里面testmethod用例
每个test开头(或_test结尾)的用例,函数(或方法)的名称就是用例的节点id,指定节点id运行用-v 参数
pytest -v test_server.py::TestClass::test_method
当然也能选择运行整个class
pytest -v test_server.py::TestClass
也能选择多个节点运行,多个节点中间空格隔开
pytest -v test_server.py::TestClass test_server.py::test_send_http
6,-k 组合调用执行部分用例
.-k 匹配用例名称
可以使用-k命令行选项指定在匹配用例名称的表达式
pytest -v -k http
您也可以运行所有的测试,根据用例名称排除掉某些用例:
pytest -k “not send_http” -v
也可以同时选择匹配 “http” 和“quick”
pytest -k “http or quick” -v
三, Pytest -fixture
下面都有实战很详细-fixture确实牛逼
pytest 相较于 unittest 最为跳跃的一点应该就是 fixture 机制
对于unittest来说,每个用例的类中都需要去写入setUp和tearDown。也就是我们所说的前置和后置,
而不可避免的,很多用例的前置和后置都是一样(例如很多用例都需要前置登录,后置退出),于是我们需要重复的复制粘贴,这样导致工作量增加,代码量也增加,界面也显得冗杂。
所以此时pytest中fixture机制便要闪亮登场了。
通俗的讲: fixture = 前置+后置
而方便的是:如果很多用例都有同样的前置和后置,那么我就只实现一个,然后需要的用例就去调用就好了。
1.机制:与测试用例同级,或者是测试用例的父级,创建一个conftest.py文件。
2.conftest.py文件里:放所有的前置和后置。 不需要用例.py文件主动引入conftest文件。
3.定义一个函数:包含前置操作+后置操作。
4.把函数声明为fixture :在函数前面加上 @pytest.fixture(作用级别=默认为function)
5.fixture的定义。
如果有返回值,那么写在yield后面。(yield的作用就相当于return)
在测试用例当中,调用有返回值的fixture函数时,函数名称就是代表返回值。
在测试用例当中,函数名称作为用例的参数即可。
1. 如下: 定义一个函数名叫open_url的fixture前后置,前置为打开链接,后置为退出浏览器
@pytest.fixture(scope=“class”) #定义scope的范围
def open\_url(): # 前置 driver = webdriver.Chrome() driver.get(url) #url为链接地址 yield driver #yield之前代码是前置,之后的代码就是后置。 # 后置 driver.quit()
这样我们就定义了一个叫做 open_url 的 fixture
2.在我们要用这个前后置的类前面 我们用@pytest.mark.usefixtures(fixture函数名)
就可以直接调用上面定义好的这个前后置
可以看到 在TestLogin 这个类中 我们不再去编写setup 和 teardown. 直接写我们的中间过程就可以了。是不是很方便了?
3.进阶方法:conftest中定义多个fixture,一个fixture可以是另一个fixture的前后置,期间还是用field隔开前后置
如上图中可以看到我class中另外还引用了一个名为refresh_page的fixture,直接上代码:
# 刷新页面 - 定义的第二个fixture @pytest.fixture def refresh\_page(open_url): yield open_url.refresh()
直接将open_url作为了另一个fixture的前置引用进来,用yield隔开,当用例中执行完open_url前后置后,再执行了一次refresh的后置。
执行顺序: open_url yield 之前代码 – 用例代码 – open_url yield 之后代码 --》 refresh_page yield 之后代码
是不是很妙,可以解决许多用例流程环环相扣时的麻烦。
4.说到上面的多个fixture调用,很多人就会疑惑,会不会fixture之间相互冲突。
当然是不会了,fixture在conftest.py当中就已经决定了他的用例域,他会主动去区分你这个fixture是作用在哪个用例域。
scope便是定义用例域的范围:
function:默认范围,每一个函数或方法都会调用,不填写时便是它
class:每一个类调用一次
module: 每一个.py文件调用一次,文件中可以有多个function和class
session:多个文件调用一次,可以跨文件,如在.py文件中,每一个.py文件就是module
范围:
session > module > class > function
所以在调用时各个fixture之间并不会相互冲突。
5,fixture的自动应用autouse
autouse调用例子:**
当管理用例比较多的时候,这种方法比较方便高效,但是用该功能时也要小心,一定要注意fixture的作用范围。需要注意的是,当使用这种方式时,就不能使用返回值的功了。autouse默认设置为False。当默认为False,就可以选择用上面两种方式来试用fixture。当设置为True时,所有的test都会自动调用这个fixture。autouse遵循scope="关键字参数"规则:当scope="session"时,无论怎样定义只运行一次;当scope="module"时,每个py文件只运行一次;当scope="class"时,每个class只运行一次(但是一个文件中包括function和class时,会在每个function(不在class中)运行一次);当scope="function"时,每个function运行一次;
‘’’
平常写自动化用例会写一些前置的fixture操作,用例需要用到就直接传该函数的参数名称就行了。当用例很多的时候,每次都传这个参数,会比较麻烦。
fixture里面有个参数autouse,默认是Fasle没开启的,可以设置为True开启自动使用fixture功能,这样用例就不用每次都去传参了
设置autouse=True
autouse设置为True,自动调用fixture功能
start设置scope为module级别,在当前.py用例模块只执行一次,autouse=True自动使用[图片]open_home设置scope为function级别,
每个用例前都调用一次,自动使用
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"])
执行结果
----开始执行module------ module : autouse ------启动浏览器------- function:test_01 --回到首页-- .----用例01----- function:test_02 --回到首页-- .----用例02----- ------结束测试 end!----------
四,参数化与数据驱动框架实现
参数化1
import pytest @pytest.mark.parametrize('test\_input,expected',[('3+5',8), ('2-1',1),('7\*5',30)]) def test\_eval(test_input,expected): assert eval(test_input)==expected ----eval把字符串转换成表达式 est_param.py::test_eval[2-1-1] test_param.py::test_eval[7\*5-30] PASSED [ 33%]PASSED [ 66%]FAILED [100%] test_param.py:3 (test_eval[7\*5-30]) 35 != 30 Expected :30 Actual :35 test_input = '7\*5', expected = 30 @pytest.mark.parametrize('test\_input,expected',[('3+5',8), ('2-1',1),('7\*5',30)]) def test\_eval(test_input,expected): > assert eval(test_input)==expected E assert 35 == 30 ----提示把30改成35 test_param.py:7: AssertionError Assertion failed
参数化2
import pytest test_user_data=['linda','sai','tom'] @pytest.fixture(scope='module') def login(request): user=request.param print('打开首页登陆%s'%user) return user #indirect=True是把login当作函数去执行 @pytest.mark.parametrize('login',test_user_data,indirect=True) def test\_cart(login): usera=login print('不同用户添加购物车%s'%usera) assert usera!='' Process finished with exit code 0 打开首页登陆linda PASSED [ 33%]不同用户添加购物车linda 打开首页登陆sai PASSED [ 66%]不同用户添加购物车sai 打开首页登陆tom PASSED [100%]不同用户添加购物车tom
参数化3
import pytest test_user_data=[ {'user':'linda','password':'8888'}, {'user':'servenruby','password':'123456'}, {'user':'test01','password':''} ] @pytest.fixture(scope='module') def login\_r(request): #可以通过dict形式,虽然传递一个参数,但通过key的方式可以达到累死传入多个参数的效果 user=request.param['user'] pwd=request.param['password'] print('\n打开首页准备登陆,登陆用户%s,密码%s'%(user,pwd)) if pwd: return True else: return False #这是pytest参数化驱动,indeirect=True是把login\_r当作函数去执行 @pytest.mark.parametrize('login\_r',test_user_data,indirect=True) def test\_cart(login_r): #登陆用例 a=login_r print('测试用例中login\_r的返回值%s'%a) assert a,'失败原因,密码为空' 开首页准备登陆,登陆用户linda,密码8888 PASSED [ 33%]测试用例中login_r的返回值True 打开首页准备登陆,登陆用户servenruby,密码123456 PASSED [ 66%]测试用例中login_r的返回值True 打开首页准备登陆,登陆用户test01,密码 FAILED [100%]测试用例中login_r的返回值False 打开首页准备登陆,登陆用户linda,密码8888 PASSED [ 33%]测试用例中login_r的返回值True 打开首页准备登陆,登陆用户servenruby,密码123456 PASSED [ 66%]测试用例中login_r的返回值True 打开首页准备登陆,登陆用户test01,密码 FAILED [100%]测试用例中login_r的返回值False test_mark_param_request2.py:19 (test_cart[login_r2]) login_r = False @pytest.mark.parametrize('login\_r',test_user_data,indirect=True) def test\_cart(login_r): #登陆用例 a=login_r print('测试用例中login\_r的返回值%s'%a) > assert a,'失败原因,密码为空' E AssertionError: 失败原因,密码为空 E assert False
参数化3*3
import pytest test_user_data1=[{'user':'linda','password':'888888'}, {'user':'servenruby','password':'123456'}, {'user':'test01','password':''}] 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['password'] print('\n用户名:%s,密码:%s'%(user,pwd)) @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_data1,indirect=True) def test\_login(login_r,query_param): #登陆用例 print(login_r) print(query_param) pytest_mark_request3.py::test_login[login_r1-query_param0] ✓ 44% ████▌ 查询的搜索词pdd None {'q': 'pdd', 'count': 3, 'page': 1} pytest_mark_request3.py::test_login[login_r1-query_param2] ✓ 56% █████▋ 用户名:linda,密码:888888 None {'q': 'pdd', 'count': 3, 'page': 1} pytest_mark_request3.py::test_login[login_r0-query_param2] ✓ 67% ██████▋ 用户名:test01,密码: None {'q': 'pdd', 'count': 3, 'page': 1} pytest_mark_request3.py::test_login[login_r2-query_param2] ✓ 78% ███████▊ 查询的搜索词阿里巴巴 None {'q': '阿里巴巴', 'count': 2, 'page': 2} pytest_mark_request3.py::test_login[login_r2-query_param1] ✓ 89% ████████▉ 查询的搜索词中国平安 None {'q': '中国平安', 'count': 3, 'page': 1} pytest_mark_request3.py::test_login[login_r2-query_param0] ✓ 100% ██████████
五,第三方插件
1,调整测试用例的执行顺序
场景:未考虑按自然顺序执行时,或想变更执行顺序,比如增加 数据的用例要先执行,再执行删除的用例。测试用例默认是按名 称顺序执行的。
• 解决:
• 安装:pip install pytest-ordering
• 在测试方法上加下面装饰器
•@pytest.mark.last —最后一个执行
• @pytest.mark.run(order=1)—第几个执行
pytest默认按字母顺序去执行的
import pytest @pytest.mark.run(order=1) def test\_01(): print('test01') @pytest.mark.run(order=2) def test\_02(): print('test01') @pytest.mark.last def test\_06(): print('test01') def test\_04(): print('test01') def test\_05(): print('test01') @pytest.mark.run(order=3) def test\_03(): print('test01') pytest_order.py::test_01 PASSED [ 16%]test01 pytest_order.py::test_02 PASSED [ 33%]test01 pytest_order.py::test_03 PASSED [ 50%]test01 pytest_order.py::test_04 PASSED [ 66%]test01 pytest_order.py::test_05 PASSED [ 83%]test01 pytest_order.py::test_06 PASSED [100%]test01
2, 执行用例遇到错误停止
• 正常全部执行完成后才能停止,如果想遇到错误时停止测试: -x;也可以当用例错误个数n达到指定数量时,停止测试:- - maxfail=n
• 执行:
• pytest -x -v -s 文件名.py ------- -x是遇到错误就停止
• pytest -x -v -s 文件名.py —maxfail=2 ------- --maxfail=2 是遇到两个错误就停止
3,执行用例失败后重新运行
**场景:
• 测试失败后要重新运行n次,要在重新运行之间添加延迟时 间,间隔n秒再运行。
• 执行:
• 安装:pip install pytest-rerunfailures
• pytest -v - -reruns 5 --reruns-delay 1 —每次等1秒 重试5次
4,多条断言前面报错后面依然执行
pip3 install pytest-assume 断言后继续执行,但要修改断言**
@pytest.mark.parametrize(('x', 'y'), [(1, 1), (1, 0), (0, 1)]) def test\_assume(x, y): pytest.assume(x == y) pytest.assume(3 == 4) pytest.assume(5 == 9)
5,多线程并行与分布式执行
场景:测试用例1000条,一个用例执行1钟,一个测试人员执行需要1000分 钟。通常我们会用人力成本换取时间成本,加几个人一起执行,时间就会缩
短。如果10人一起执行只需要100分钟,这就是一种并行测试,分布式场景。
解决:pytest分布式执行插件:pytest-xdist,多个CPU或主机执行
前提:用例之间都是独立的,没有先后顺序,随机都能执行,可重复运行不 影响其他用例。
安装:Pip3 install pytest-xdist
• 多个CPU并行执行用例,直接加-n 3是并行数量:pytest -n 3 • 在多个终端下一起执行
import pytest import time @pytest.mark.parametrize('x',list(range(10))) def test\_somethins(x): time.sleep(1) pytest -v -s -n 5 test_xsdist.py ----一次执行5个
运行以下代码,项目结构如下
web_conf_py是项目工程名称
│ conftest.py │ __init__.py │ ├─baidu │ │ conftest.py │ │ test_1_baidu.py │ │ test_2.py │ │ __init__.py │ ├─blog │ │ conftest.py │ │ test_2_blog.py │ │ __init__.py
代码参考:
# web\_conf\_py/conftest.py import pytest @pytest.fixture(scope="session") def start(): print("\n打开首页") return "yoyo" # web\_conf\_py/baidu/conftest.py import pytest @pytest.fixture(scope="session") def open\_baidu(): print("打开百度页面\_session") # web\_conf\_py/baidu/test\_1\_baidu.py import pytest import time def test\_01(start, open_baidu): print("测试用例test\_01") time.sleep(1) assert start == "yoyo" def test\_02(start, open_baidu): print("测试用例test\_02") time.sleep(1) assert start == "yoyo" if __name__ == "\_\_main\_\_": pytest.main(["-s", "test\_1\_baidu.py"]) # web\_conf\_py/baidu/test\_2.py import pytest import time def test\_06(start, open_baidu): print("测试用例test\_01") time.sleep(1) assert start == "yoyo" def test\_07(start, open_baidu): print("测试用例test\_02") time.sleep(1) assert start == "yoyo" if __name__ == "\_\_main\_\_": pytest.main(["-s", "test\_2.py"]) # web\_conf\_py/blog/conftest.py import pytest @pytest.fixture(scope="function") def open\_blog(): print("打开blog页面\_function") # web\_conf\_py/blog/test\_2\_blog.py import pytest import time def test\_03(start, open_blog): print("测试用例test\_03") time.sleep(1) assert start == "yoyo" def test\_04(start, open_blog): print("测试用例test\_04") time.sleep(1) assert start == "yoyo" def test\_05(start, open_blog): '''跨模块调用baidu模块下的conftest''' print("测试用例test\_05,跨模块调用baidu") time.sleep(1) assert start == "yoyo" if __name__ == "\_\_main\_\_": pytest.main(["-s", "test\_2\_blog.py"])
正常运行需要消耗时间:7.12 seconds
E:\YOYO\web_conf_py>pytest ============================= test session starts ============================= platform win32 -- Python 3.6.0, pytest-3.6.3, py-1.5.4, pluggy-0.6.0 rootdir: E:\YOYO\web_conf_py, inifile: plugins: xdist-1.23.2, metadata-1.7.0, html-1.19.0, forked-0.2 collected 7 items baidu\test_1_baidu.py .. [ 28%] baidu\test_2.py .. [ 57%] blog\test_2_blog.py ... [100%] ========================== 7 passed in 7.12 seconds ===========================
设置并行运行数量为3,消耗时间:3.64 seconds,大大的缩短了用例时间
E:\YOYO\web_conf_py>pytest -n 3 ============================= test session starts ============================= platform win32 -- Python 3.6.0, pytest-3.6.3, py-1.5.4, pluggy-0.6.0 rootdir: E:\YOYO\web_conf_py, inifile: plugins: xdist-1.23.2, metadata-1.7.0, html-1.19.0, forked-0.2 gw0 [7] / gw1 [7] / gw2 [7] scheduling tests via LoadScheduling ....... [100%] ========================== 7 passed in 3.64 seconds ===========================
6,其他有意思的插件
这里就不多说了,喜欢的可以自己研究下
7,使用pytest执行unittest的测试用例
执行unitest就和原来一样,尽量不要混合使用搞那些花里胡哨的,用哪个就哪个,就不多说了
8,pytest-html生成报告
pytest-HTML是一个插件,pytest用于生成测试结果的HTML报告。兼容Python 2.7,3.6
pytest-html
1.github上源码地址【https://github.com/pytest-dev/pytest-html】
2.pip安装
$ pip install pytest-html
3.执行方法
$ pytest --html=report.html
html报告
1.打开cmd,cd到需要执行pytest用例的目录,执行指令:pytest --html=report.html
2.执行完之后,在当前目录会生成一个report.html的报告文件,显示效果如下
指定报告路径
1.直接执行"pytest --html=report.html"生成的报告会在当前脚本的同一路径,如果想指定报告的存放位置,放到当前脚本的同一目录下的report文件夹里
pytest --html=./report/report.html
2.如果想指定执行某个.py文件用例或者某个文件夹里面的所有用例,需加个参数。具体规则参考【pytest文档2-用例运行规则】
报告独立显示
1.上面方法生成的报告,css是独立的,分享报告的时候样式会丢失,为了更好的分享发邮件展示报告,可以把css样式合并到html里
$ pytest --html=report.html --self-contained-html
显示选项
默认情况下,“ 结果”表中的所有行都将被展开,但具测试通过的行除外Passed。
可以使用查询参数自定义此行为:?collapsed=Passed,XFailed,Skipped。
更多功能
1.更多功能查看官方文档【https://github.com/pytest-dev/pytest-html】
六,日志管理及代码覆盖率
1, pytest中logging的应用
2, 日志及级别的含义
自动化测试用例的调试信息非常有用,可以让我们知道现在的运行情况到,执行到哪步以及相应的出错信息等,可以在pytest里面,有时并不会输出所有信息,比如默认情况下pass的测试用例是没有print输出的。本文将介绍如何在pytest里面实时显示所有的log信息。
1. 用print输出log信息
slowTest_print.py
import time def test\_1(): print 'test\_1' time.sleep(1) print 'after 1 sec' time.sleep(1) print 'after 2 sec' time.sleep(1) print 'after 3 sec' assert 1, 'should pass' def test\_2(): print 'in test\_2' time.sleep(1) print 'after 1 sec' time.sleep(1) print 'after 2 sec' time.sleep(1) print 'after 3 sec' assert 0, 'failing for demo purposes'
运行上述程序,pytest会capture所有的输出,保存直到所有的测试用例都执行结束,并且只输出那些失败的测试用例的信息,对于成功的测试用例,没有print的信息显示。
从下面的运行结果,如果需要查看test_1()的运行情况,没有log信息可看,print没有显示。
C:\Users\yatyang\PycharmProjects\pytest_example>pytest -v slowTest_print.py ============================= test session starts ============================= platform win32 -- Python 2.7.13, pytest-3.0.6, py-1.4.32, pluggy-0.4.0 -- C:\Python27\python.exe cachedir: .cache metadata: {'Python': '2.7.13', 'Platform': 'Windows-7-6.1.7601-SP1', 'Packages': {'py': '1.4.32', 'pytest': '3.0.6', 'pluggy': '0.4.0'}, 'JAVA\_HOME': 'C:\\Program Files (x86)\\Java\\jd k1.7.0\_01', 'Plugins': {'html': '1.14.2', 'metadata': '1.3.0'}} rootdir: C:\Users\yatyang\PycharmProjects\pytest_example, inifile: plugins: metadata-1.3.0, html-1.14.2 collected 2 items slowTest_print.py::test_1 PASSED slowTest_print.py::test_2 FAILED ================================== FAILURES =================================== ___________________________________ test_2 ____________________________________ def test\_2(): print 'in test\_2' time.sleep(1) print 'after 1 sec' time.sleep(1) print 'after 2 sec' time.sleep(1) print 'after 3 sec' > assert 0, 'failing for demo purposes' E AssertionError: failing for demo purposes E assert 0 slowTest_print.py:22: AssertionError ---------------------------- Captured stdout call ----------------------------- in test_2 after 1 sec after 2 sec after 3 sec ===================== 1 failed, 1 passed in 6.45 seconds ====================== C:\Users\yatyang\PycharmProjects\pytest_example>
我们可以用‘-s’参数或者 ‘–capture=no’,这样就可以输出所有测试用的print信息。但是pytest还是会等着所有的测试用例都执行完毕才会显示运行结果。可以看到下面的test_1也显示出print的相关信息。
C:\Users\yatyang\PycharmProjects\pytest_example>py.test --capture=no slowTest_print.py ============================= test session starts ============================= platform win32 -- Python 2.7.13, pytest-3.0.6, py-1.4.32, pluggy-0.4.0 metadata: {'Python': '2.7.13', 'Platform': 'Windows-7-6.1.7601-SP1', 'Packages': {'py': '1.4.32', 'pytest': '3.0.6', 'pluggy': '0.4.0'}, 'JAVA\_HOME': 'C:\\Program Files (x86)\\Java\\jd k1.7.0\_01', 'Plugins': {'html': '1.14.2', 'metadata': '1.3.0'}} rootdir: C:\Users\yatyang\PycharmProjects\pytest_example, inifile: plugins: metadata-1.3.0, html-1.14.2 collected 2 items slowTest_print.py test_1 after 1 sec after 2 sec after 3 sec .in test_2 after 1 sec after 2 sec after 3 sec F ================================== FAILURES =================================== ___________________________________ test_2 ____________________________________ def test\_2(): print 'in test\_2' time.sleep(1) print 'after 1 sec' time.sleep(1) print 'after 2 sec' time.sleep(1) print 'after 3 sec' > assert 0, 'failing for demo purposes' E AssertionError: failing for demo purposes E assert 0 slowTest_print.py:22: AssertionError ===================== 1 failed, 1 passed in 6.17 seconds ======================
2. Python Logging用法
一般情况下,一些程序的调试过程中我们会让它输出一些信息,特别是一些大型的程序,我们通过这些信息可以了解程序的运行情况,python提供了一个日志模块logging,它可以把我们想要的信息全部保存到一个日志文件中,方便查看。
import logging logging.debug('This is debug message') logging.info('This is info message') logging.warning('This is warning message')
屏幕上打印:
WARNING:root:This is warning message
默认情况下,logging将日志打印到屏幕,日志级别为WARNING;
日志级别大小关系为:CRITICAL > ERROR > WARNING > INFO > DEBUG > NOTSET,当然也可以自己定义日志级别。
3. 在pytest中用logging代替print
我们现在来看看在pytest的测试用例里面用logging的输出代替print,有什么不同。
slowTest_logging.py
import time import logging logging.basicConfig(level=logging.DEBUG) def test\_1(): log = logging.getLogger('test\_1') time.sleep(1) log.debug('after 1 sec') time.sleep(1) log.debug('after 2 sec') time.sleep(1) log.debug('after 3 sec') assert 1, 'should pass' def test\_2(): log = logging.getLogger('test\_2') time.sleep(1) log.debug('after 1 sec') time.sleep(1) log.debug('after 2 sec') time.sleep(1) log.debug('after 3 sec') assert 0, 'failing for demo purposes'
运行结果如下,log信息的显示是不是可读性更好了呢。可是pytest还是要等所有的结果都运行完毕才完全输出到屏幕上,没法看到实时的运行情况。比如现在要测试一个新的image,不知道quality如何,如果测试用例非常多,测试人员就得一直等,也许前面的一些测试用都失败就可以停止执行了。那怎么实现实时显示呢?请看方法4。
C:\Users\yatyang\PycharmProjects\pytest_example>pytest slowTest_logging.py ============================= test session starts ============================= platform win32 -- Python 2.7.13, pytest-3.0.6, py-1.4.32, pluggy-0.4.0 metadata: {'Python': '2.7.13', 'Platform': 'Windows-7-6.1.7601-SP1', 'Packages': {'py': '1.4.32', 'pytest': '3.0.6', 'pluggy': '0.4.0'}, 'JAVA\_HOME': 'C:\\Program Files (x86)\\Java\\jd k1.7.0\_01', 'Plugins': {'html': '1.14.2', 'metadata': '1.3.0'}} rootdir: C:\Users\yatyang\PycharmProjects\pytest_example, inifile: plugins: metadata-1.3.0, html-1.14.2 collected 2 items slowTest_logging.py .F ================================== FAILURES =================================== ___________________________________ test_2 ____________________________________ def test\_2(): log = logging.getLogger('test\_2') time.sleep(1) log.debug('after 1 sec') time.sleep(1) log.debug('after 2 sec') time.sleep(1) log.debug('after 3 sec') > assert 0, 'failing for demo purposes' E AssertionError: failing for demo purposes E assert 0 slowTest_logging.py:25: AssertionError ---------------------------- Captured stderr call ----------------------------- DEBUG:test_2:after 1 sec DEBUG:test_2:after 2 sec DEBUG:test_2:after 3 sec ===================== 1 failed, 1 passed in 6.37 seconds ======================
C:\Users\yatyang\PycharmProjects\pytest_example>pytest -s slowTest_logging.py ============================= test session starts ============================= platform win32 -- Python 2.7.13, pytest-3.0.6, py-1.4.32, pluggy-0.4.0 metadata: {'Python': '2.7.13', 'Platform': 'Windows-7-6.1.7601-SP1', 'Packages': {'py': '1.4.32', 'pytest': '3.0.6', 'pluggy': '0.4.0'}, 'JAVA\_HOME': 'C:\\Program Files (x86)\\Java\\jd k1.7.0\_01', 'Plugins': {'html': '1.14.2', 'metadata': '1.3.0'}} rootdir: C:\Users\yatyang\PycharmProjects\pytest_example, inifile: plugins: metadata-1.3.0, html-1.14.2 collected 2 items slowTest_logging.py DEBUG:test_1:after 1 sec DEBUG:test_1:after 2 sec DEBUG:test_1:after 3 sec .DEBUG:test_2:after 1 sec DEBUG:test_2:after 2 sec DEBUG:test_2:after 3 sec F ================================== FAILURES =================================== ___________________________________ test_2 ____________________________________ def test\_2(): log = logging.getLogger('test\_2') time.sleep(1) log.debug('after 1 sec') time.sleep(1) log.debug('after 2 sec') time.sleep(1) log.debug('after 3 sec') > assert 0, 'failing for demo purposes' E AssertionError: failing for demo purposes E assert 0 slowTest_logging.py:25: AssertionError ===================== 1 failed, 1 passed in 6.18 seconds ======================
4. pytest用logging和–capture=no实现实时输出log信息
请自己去运行下面的程序吧,可以看到该程序是实时输出当前测试用例执行的情况。
C:\Users\yatyang\PycharmProjects\pytest_example>pytest -s slowTest_logging.py ============================= test session starts ============================= platform win32 -- Python 2.7.13, pytest-3.0.6, py-1.4.32, pluggy-0.4.0 metadata: {'Python': '2.7.13', 'Platform': 'Windows-7-6.1.7601-SP1', 'Packages': {'py': '1.4.32', 'pytest': '3.0.6', 'pluggy': '0.4.0'}, 'JAVA\_HOME': 'C:\\Program Files (x86)\\Java\\jd k1.7.0\_01', 'Plugins': {'html': '1.14.2', 'metadata': '1.3.0'}} rootdir: C:\Users\yatyang\PycharmProjects\pytest_example, inifile: plugins: metadata-1.3.0, html-1.14.2 collected 2 items slowTest_logging.py DEBUG:test_1:after 1 sec DEBUG:test_1:after 2 sec DEBUG:test_1:after 3 sec .DEBUG:test_2:after 1 sec DEBUG:test_2:after 2 sec DEBUG:test_2:after 3 sec F ================================== FAILURES =================================== ___________________________________ test_2 ____________________________________ def test\_2(): log = logging.getLogger('test\_2') time.sleep(1) log.debug('after 1 sec') time.sleep(1) log.debug('after 2 sec') time.sleep(1) log.debug('after 3 sec') > assert 0, 'failing for demo purposes' E AssertionError: failing for demo purposes E assert 0 slowTest_logging.py:25: AssertionError ===================== 1 failed, 1 passed in 6.20 seconds ======================
5.总结
在写自动化测试用例时,添加有用的log信息是非常有必要的。比如在初期的调试过程,能够一旦运行有问题,就可以获取到精确的调试信息。后期在稳定的运行中,其他测试人员来运行也可以很容易上手,所以大家一定要重视测试用例的调试信息。
通过本文,应该知道如何用pytest,logging和–capture=no实现运行测试用例的实时输出所有的log信息。
3, 代码覆盖率-多用在单元测试中
一,上篇(---- pytest-cov)
简介:
pytest-cov 是pytest的一个插件,其本质也是引用 python coverage 库 用来统计代码覆盖率。以下这篇文章只供理解,真实项目的话,我们都是用api调用接口的,所以真实项目使用会更复杂一些,这个待下次说明。
另外说明:coverage 是在覆盖率是语句覆盖的一种,不能对你的逻辑做判读,真实意义的话,需要多结合项目本身,这个覆盖率数据没有很强大说服力,不要盲目追求。
一般来说:
路径覆盖率 > 判定覆盖 > 语句覆盖
安装
pip install pytest-cover
安装完后有
py.test -h 可以看到多了以下的用法,说明安装成功: coverage reporting with distributed testing support:
范例
新建三个文件,cau.py 与test_conver.py 在同一个目录code下。run.py文件在上一级目录pp下。
代码关系如下。
1.新建函数文件cau.py
#!/usr/bin/env python # -\*- coding: utf-8 -\*- def cau (type,n1, n2): if type==1: a=n1 + n2 elif type==2: a = n1 - n2 else: a=n1 \* n2 return a
2.新建test_conver.py测试文件:
#!/usr/bin/env python # -\*- coding: utf-8 -\*- from code.cau import cau class Test\_cover: def test\_add(self): a=cau(1,2,3) assert a==3
3.新建执行脚本run.py
#!/usr/bin/env ```python # -\*- coding: utf-8 -\*- import pytest if __name__=='\_\_main\_\_': pytest.main(["--cov=./code/" ,"--cov-report=html","--cov-config=./code/.coveragerc"] ) # 执行某个目录下case
说明:–cov参数 后面接的是测试的目录 (经给测试,不能指定某个特定的文件。),程序代码跟测试脚本必须在同一个文件下。 --cov-report=html 生成报告 ,只需要python run.py 就可以运行
coveragerc 意思是跳过某些脚本的覆盖率测试。此处跳过test_cover.py文件跟init文件。
内容如下:
[run] omit = tests/\* \*/__init__.py \*/test_cover.py
结果
生成完后可以直接点击indexhtml
可以看到如下的执行情况,绿色代表运行,红色代表未被执行,自己检查下代码逻辑,可以得出该结果是正确的。
二:下篇(— coverage.py api)
使用pytest-cov 无法统计用 api调用服务的测试脚本所覆盖率,但大部分的项目基本也是使用api调用。所以我们额外需要使用coverage.py api 来统计。
当你安装pytest-cov时,已经默认安装了coverage 这个库。
服务启动
要想扫描到代码,必须在服务启动的时候要插入coverage相关配置。
我这边是flask 启动的,所以在flask启动的代码上添加,如下:
if __name__ == '\_\_main\_\_': cov = Coverage() cov.start() # 开始检测代码 print ("qidong") app.run(debug=True, host='0.0.0.0',port=9098) #原本只有这一行 cov.stop() # 停止纪录 print ("guanbi") cov.save() # 保存在 .coverage 中 print ("save") cov.html_report() # 生成 HTML 报告
原本我们是python xx.py 这样启动,但现在不可以。
需要改成这样,source 表示目录,xx表示执行文件。
coverage run --source='/xxx/' xx.py ![img](https://img-blog.csdnimg.cn/img_convert/f2cb8122e10dd2f34d7f8bcf6b483181.png) ![img](https://img-blog.csdnimg.cn/img_convert/9b12496b59db2017705b08ca27528347.png) **既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!** **由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新** **[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)** watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNjEwMTY3,size_16,color_FFFFFF,t_70) 可以看到如下的执行情况,绿色代表运行,红色代表未被执行,自己检查下代码逻辑,可以得出该结果是正确的。 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190923173444582.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNjEwMTY3,size_16,color_FFFFFF,t_70) **二:下篇(— coverage.py api)** 使用pytest-cov 无法统计用 api调用服务的测试脚本所覆盖率,但大部分的项目基本也是使用api调用。所以我们额外需要使用coverage.py api 来统计。 当你安装pytest-cov时,已经默认安装了coverage 这个库。 服务启动 要想扫描到代码,必须在服务启动的时候要插入coverage相关配置。 我这边是flask 启动的,所以在flask启动的代码上添加,如下:
if name == ‘__main__’:
cov = Coverage()
cov.start() # 开始检测代码
print (“qidong”)
app.run(debug=True, host=‘0.0.0.0’,port=9098) #原本只有这一行
cov.stop() # 停止纪录
print (“guanbi”)
cov.save() # 保存在 .coverage 中
print (“save”)
cov.html_report() # 生成 HTML 报告
原本我们是python xx.py 这样启动,但现在不可以。 需要改成这样,source 表示目录,xx表示执行文件。
coverage run --source=‘/xxx/’ xx.py
[外链图片转存中…(img-Z7cHZDkW-1715605159598)]
[外链图片转存中…(img-sYypwUNE-1715605159599)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
如果你需要这些资料,可以戳这里获取