六,日志管理及代碼覆蓋率
1, pytest中l(wèi)ogging的應(yīng)用
2, 日志及級(jí)別的含義
自動(dòng)化測(cè)試用例的調(diào)試信息非常有用,可以讓我們知道現(xiàn)在的運(yùn)行情況到,執(zhí)行到哪步以及相應(yīng)的出錯(cuò)信息等,可以在pytest里面,有時(shí)并不會(huì)輸出所有信息,比如默認(rèn)情況下pass的測(cè)試用例是沒有print輸出的。本文將介紹如何在pytest里面實(shí)時(shí)顯示所有的log信息。
- 用print輸出log信息
slowTest_print.py1. 用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'
運(yùn)行上述程序,pytest會(huì)capture所有的輸出,保存直到所有的測(cè)試用例都執(zhí)行結(jié)束,并且只輸出那些失敗的測(cè)試用例的信息,對(duì)于成功的測(cè)試用例,沒有print的信息顯示。
從下面的運(yùn)行結(jié)果,如果需要查看test_1()的運(yùn)行情況,沒有l(wèi)og信息可看,print沒有顯示。
C:\Users\yatyang\PycharmProjects\pytest_example>pytest -v slowTest_print.py
以用‘-s’參數(shù)或者 ‘–capture=no’,這樣就可以輸出所有測(cè)試用的print信息。但是pytest還是會(huì)等著所有的測(cè)試用例都執(zhí)行完畢才會(huì)顯示運(yùn)行結(jié)果??梢钥吹较旅娴膖est_1也顯示出print的相關(guān)信息。
C:\Users\yatyang\PycharmProjects\pytest_example>py.test --capture=no slowTest_print.py
- Python Logging用法
一般情況下,一些程序的調(diào)試過程中我們會(huì)讓它輸出一些信息,特別是一些大型的程序,我們通過這些信息可以了解程序的運(yùn)行情況,python提供了一個(gè)日志模塊logging,它可以把我們想要的信息全部保存到一個(gè)日志文件中,方便查看。
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
默認(rèn)情況下,logging將日志打印到屏幕,日志級(jí)別為WARNING;
日志級(jí)別大小關(guān)系為:CRITICAL > ERROR > WARNING > INFO > DEBUG > NOTSET,當(dāng)然也可以自己定義日志級(jí)別。
- 在pytest中用logging代替print
我們現(xiàn)在來看看在pytest的測(cè)試用例里面用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'
運(yùn)行結(jié)果如下,log信息的顯示是不是可讀性更好了呢??墒莗ytest還是要等所有的結(jié)果都運(yùn)行完畢才完全輸出到屏幕上,沒法看到實(shí)時(shí)的運(yùn)行情況。比如現(xiàn)在要測(cè)試一個(gè)新的image,不知道quality如何,如果測(cè)試用例非常多,測(cè)試人員就得一直等,也許前面的一些測(cè)試用都失敗就可以停止執(zhí)行了。那怎么實(shí)現(xiàn)實(shí)時(shí)顯示呢?請(qǐng)看方法4。
C:\Users\yatyang\PycharmProjects\pytest_example>pytest slowTest_logging.py
- pytest用logging和–capture=no實(shí)現(xiàn)實(shí)時(shí)輸出log信息
請(qǐng)自己去運(yùn)行下面的程序吧,可以看到該程序是實(shí)時(shí)輸出當(dāng)前測(cè)試用例執(zhí)行的情況。
C:\Users\yatyang\PycharmProjects\pytest_example>pytest -s slowTest_logging.py
5.總結(jié)
在寫自動(dòng)化測(cè)試用例時(shí),添加有用的log信息是非常有必要的。比如在初期的調(diào)試過程,能夠一旦運(yùn)行有問題,就可以獲取到精確的調(diào)試信息。后期在穩(wěn)定的運(yùn)行中,其他測(cè)試人員來運(yùn)行也可以很容易上手,所以大家一定要重視測(cè)試用例的調(diào)試信息。
通過本文,應(yīng)該知道如何用pytest,logging和–capture=no實(shí)現(xiàn)運(yùn)行測(cè)試用例的實(shí)時(shí)輸出所有的log信息。
3, 代碼覆蓋率-多用在單元測(cè)試中
一,上篇(---- pytest-cov)
簡(jiǎn)介:
pytest-cov 是pytest的一個(gè)插件,其本質(zhì)也是引用 python coverage 庫 用來統(tǒng)計(jì)代碼覆蓋率。以下這篇文章只供理解,真實(shí)項(xiàng)目的話,我們都是用api調(diào)用接口的,所以真實(shí)項(xiàng)目使用會(huì)更復(fù)雜一些,這個(gè)待下次說明。
一般來說:
路徑覆蓋率 > 判定覆蓋 > 語句覆蓋
安裝
pip install pytest-cover
安裝完后有
py.test -h 可以看到多了以下的用法,說明安裝成功:
coverage reporting with distributed testing support:
另外說明:coverage 是在覆蓋率是語句覆蓋的一種,不能對(duì)你的邏輯做判讀,真實(shí)意義的話,需要多結(jié)合項(xiàng)目本身,這個(gè)覆蓋率數(shù)據(jù)沒有很強(qiáng)大說服力,不要盲目追求。
范例
新建三個(gè)文件,cau.py 與test_conver.py 在同一個(gè)目錄code下。run.py文件在上一級(jí)目錄pp下。
代碼關(guān)系如下。

1.新建函數(shù)文件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測(cè)試文件:
#!/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.新建執(zhí)行腳本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"] ) # 執(zhí)行某個(gè)目錄下case
說明:–cov參數(shù) 后面接的是測(cè)試的目錄 (經(jīng)給測(cè)試,不能指定某個(gè)特定的文件。),程序代碼跟測(cè)試腳本必須在同一個(gè)文件下。 --cov-report=html 生成報(bào)告 ,只需要python run.py 就可以運(yùn)行
coveragerc 意思是跳過某些腳本的覆蓋率測(cè)試。此處跳過test_cover.py文件跟init文件。
內(nèi)容如下:
[run]
omit =
tests/*
*/__init__.py
*/test_cover.py
結(jié)果
生成完后可以直接點(diǎn)擊indexhtml
可以看到如下的執(zhí)行情況,綠色代表運(yùn)行,紅色代表未被執(zhí)行,自己檢查下代碼邏輯,可以得出該結(jié)果是正確的。
二:下篇(— coverage.py api)
使用pytest-cov 無法統(tǒng)計(jì)用 api調(diào)用服務(wù)的測(cè)試腳本所覆蓋率,但大部分的項(xiàng)目基本也是使用api調(diào)用。所以我們額外需要使用coverage.py api 來統(tǒng)計(jì)。
當(dāng)你安裝pytest-cov時(shí),已經(jīng)默認(rèn)安裝了coverage 這個(gè)庫。
服務(wù)啟動(dòng)
要想掃描到代碼,必須在服務(wù)啟動(dòng)的時(shí)候要插入coverage相關(guān)配置。
我這邊是flask 啟動(dòng)的,所以在flask啟動(dòng)的代碼上添加,如下:
if __name__ == '__main__':
cov = Coverage()
cov.start() # 開始檢測(cè)代碼
print ("qidong")
app.run(debug=True, host='0.0.0.0',port=9098) #原本只有這一行
cov.stop() # 停止紀(jì)錄
print ("guanbi")
cov.save() # 保存在 .coverage 中
print ("save")
cov.html_report() # 生成 HTML 報(bào)告
原本我們是python xx.py 這樣啟動(dòng),但現(xiàn)在不可以。
需要改成這樣,source 表示目錄,xx表示執(zhí)行文件。
coverage run --source='/xxx/' xx.py
啟動(dòng)運(yùn)行圖如下:
然后調(diào)用你的自動(dòng)化腳本(自動(dòng)化腳本是直接調(diào)的該服務(wù)提供的api 。)
自動(dòng)化如果正常運(yùn)行,能看到運(yùn)行的請(qǐng)求
以上說明你的腳本跟服務(wù)是沒問題的
ctr-c停掉該腳本后,最后顯示save,如果顯示”Coverage.py warning: No data was collected. (no-data-collected)“ 說明的服務(wù)運(yùn)行方式有問題,coverage 服務(wù)沒有運(yùn)行到你代碼
報(bào)告生成
輸入以下命令
coverage report
最后一步最后輸入
coverage html
這樣就可以省 html 文件了。
導(dǎo)出在window上看,具體點(diǎn)擊某個(gè)文件,點(diǎn)擊run,你可以看到綠色的就是運(yùn)行的。但有問題是,你會(huì)發(fā)現(xiàn)有些代碼應(yīng)該是要被執(zhí)行,但卻沒有被執(zhí)行。所以coverage的數(shù)據(jù)準(zhǔn)不準(zhǔn)很難說。
4,allure測(cè)試報(bào)告框架
pytest+allure現(xiàn)在都是結(jié)合jenkins來搞的,很簡(jiǎn)單相信大家都會(huì),不會(huì)的老哥可以去看我的另一個(gè)博客持續(xù)集成里的有寫
5,定制報(bào)告
定制報(bào)告
Feature: 標(biāo)注主要功能模塊
Story: 標(biāo)注Features功能模塊下的分支功能
Severity: 標(biāo)注測(cè)試用例的重要級(jí)別
Step: 標(biāo)注測(cè)試用例的重要步驟
Issue和TestCase: 標(biāo)注Issue、Case,可加入U(xiǎn)RL
1、Features定制詳解
# -*- coding: utf-8 -*-
# @Time : 2018/8/17 上午10:10
# @Author : WangJuan
# @File : test_case.py
import allure
import pytest
@allure.feature('test_module_01')
def test_case_01():
"""
用例描述:Test case 01
"""
assert 0
@allure.feature('test_module_02')
def test_case_02():
"""
用例描述:Test case 02
"""
assert 0 == 0
if __name__ == '__main__':
pytest.main(['-s', '-q', '--alluredir', './report/xml'])
添加feature,Report展示見下圖。
2、Story定制詳解
# -*- coding: utf-8 -*-
# @Time : 2018/8/17 上午10:10
# @Author : WangJuan
# @File : test_case.py
import allure
import pytest
@allure.feature('test_module_01')
@allure.story('test_story_01')
def test_case_01():
"""
用例描述:Test case 01
"""
assert 0
@allure.feature('test_module_01')
@allure.story('test_story_02')
def test_case_02():
"""
用例描述:Test case 02
"""
assert 0 == 0
if __name__ == '__main__':
pytest.main(['-s', '-q', '--alluredir', './report/xml'])
添加story,Report展示見下圖。
3、用例標(biāo)題和用例描述定制詳解
# -*- coding: utf-8 -*-
# @Time : 2018/8/17 上午10:10
# @Author : WangJuan
# @File : test_case.py
import allure
import pytest
@allure.feature('test_module_01')
@allure.story('test_story_01')
#test_case_01為用例title
def test_case_01():
"""
用例描述:這是用例描述,Test case 01,描述本人
"""
#注釋為用例描述
assert 0
if __name__ == '__main__':
pytest.main(['-s', '-q', '--alluredir', './report/xml'])
添加用例標(biāo)題和用例描述,Report展示見下圖。
4 、Severity定制詳解
Allure中對(duì)嚴(yán)重級(jí)別的定義:
1、 Blocker級(jí)別:中斷缺陷(客戶端程序無響應(yīng),無法執(zhí)行下一步操作)
2、 Critical級(jí)別:臨界缺陷( 功能點(diǎn)缺失)
3、 Normal級(jí)別:普通缺陷(數(shù)值計(jì)算錯(cuò)誤)
4、 Minor級(jí)別:次要缺陷(界面錯(cuò)誤與UI需求不符)
5、 Trivial級(jí)別:輕微缺陷(必輸項(xiàng)無提示,或者提示不規(guī)范)
# -*- coding: utf-8 -*-
# @Time : 2018/8/17 上午10:10
# @Author : WangJuan
# @File : test_case.py
import allure
import pytest
@allure.feature('test_module_01')
@allure.story('test_story_01')
@allure.severity('blocker')
def test_case_01():
"""
用例描述:Test case 01
"""
assert 0
@allure.feature('test_module_01')
@allure.story('test_story_01')
@allure.severity('critical')
def test_case_02():
"""
用例描述:Test case 02
"""
assert 0 == 0
@allure.feature('test_module_01')
@allure.story('test_story_02')
@allure.severity('normal')
def test_case_03():
"""
用例描述:Test case 03
"""
assert 0
@allure.feature('test_module_01')
@allure.story('test_story_02')
@allure.severity('minor')
def test_case_04():
"""
用例描述:Test case 04
"""
assert 0 == 0
if __name__ == '__main__':
pytest.main(['-s', '-q', '--alluredir', './report/xml'])
添加Severity,Report展示見下圖。
5****Step定制詳解
# -*- coding: utf-8 -*-
# @Time : 2018/8/17 上午10:10
# @Author : WangJuan
# @File : test_case.py
import allure
import pytest
@allure.step("字符串相加:{0},{1}")
# 測(cè)試步驟,可通過format機(jī)制自動(dòng)獲取函數(shù)參數(shù)
def str_add(str1, str2):
if not isinstance(str1, str):
return "%s is not a string" % str1
if not isinstance(str2, str):
return "%s is not a string" % str2
return str1 + str2
@allure.feature('test_module_01')
@allure.story('test_story_01')
@allure.severity('blocker')
def test_case():
str1 = 'hello'
str2 = 'world'
assert str_add(str1, str2) == 'helloworld'
if __name__ == '__main__':
pytest.main(['-s', '-q', '--alluredir', './report/xml'])
添加Step,Report展示見下圖。
6、Issue和TestCase定制詳解
# -*- coding: utf-8 -*-
# @Time : 2018/8/17 上午10:10
# @Author : WangJuan
# @File : test_case.py
import allure
import pytest
@allure.step("字符串相加:{0},{1}") # 測(cè)試步驟,可通過format機(jī)制自動(dòng)獲取函數(shù)參數(shù)
def str_add(str1, str2):
print('hello')
if not isinstance(str1, str):
return "%s is not a string" % str1
if not isinstance(str2, str):
return "%s is not a string" % str2
return str1 + str2
@allure.feature('test_module_01')
@allure.story('test_story_01')
@allure.severity('blocker')
@allure.issue("http://www.baidu.com")
@allure.testcase("http://www.testlink.com")
def test_case():
str1 = 'hello'
str2 = 'world'
assert str_add(str1, str2) == 'helloworld'
if __name__ == '__main__':
pytest.main(['-s', '-q', '--alluredir', './report/xml'])
添加Issue和TestCase,Report展示見下圖。
**8、attach定制詳解
file = open('../test.png', 'rb').read()
allure.attach('test_img', file, allure.attach_type.PNG)
在報(bào)告中增加附件:allure.attach(’arg1’,’arg2’,’arg3’):
arg1:是在報(bào)告中顯示的附件名稱
arg2:表示添加附件的內(nèi)容
arg3:表示添加的類型(支持:HTML,JPG,PNG,JSON,OTHER,TEXTXML)
添加attach參數(shù),Report展示見下圖。
6,pytest運(yùn)行指定用例
隨著軟件功能的增加,模塊越來越多,也意味用例越來越多,為了節(jié)約執(zhí)行時(shí)間,快速得到測(cè)試報(bào)告與結(jié)果,在工作中可以通過運(yùn)行指定用例,達(dá)到快速執(zhí)行用例
例子目錄
spec_sub1_modul_test.py
#coding: UTF-8
import pytest
def test_004_spec():
assert 1==1
def test_005_spec():
assert True==False
class Test_Class():
def test_006_spec(self):
assert 'G' in "Goods"
spec_sub2_modul_test.py
#coding: UTF-8
import pytest
def test_007_spec():
assert 1==1
def test_008_spec():
assert True==False
class Test_Class():
def test_009_spec(self):
assert 'G' in "Goods"
spec_001_modul_test
#coding: UTF-8
import pytest
def test_001_spec():
assert 1==1
def test_002_spec():
assert True==False
class Test_Class():
def test_003_spec(self):
assert 'H' in "Hell,Jerry"
運(yùn)行指定模塊
if __name__ == '__main__':
pytest.main("-v -s spec_001_modul_test.py")
運(yùn)行批量文件夾(運(yùn)行當(dāng)前文件夾包括子文件夾所有用例)
#coding: UTF-8
import pytest
if __name__ == '__main__':
pytest.main("-v -s ./")
運(yùn)行指定文件夾(subpath1目錄下面所有用例)
#coding: UTF-8
import pytest
if __name__ == '__main__':
pytest.main("-v -s subpath1/")
運(yùn)行模塊中指定用例 (運(yùn)行模塊中test_001_spec用例)
if __name__ == '__main__':
pytest.main("-v -s spec_001_modul_test.py::test_001_spec")
運(yùn)行class中指定的用例(運(yùn)行模塊中Test_Class類test_003_spec方法)
if __name__ == '__main__':
pytest.main("-v -s spec_001_modul_test.py::Test_Class::test_003_spec")
模糊匹配運(yùn)行用例(匹配當(dāng)前目錄下面包含)
if __name__ == '__main__':
#運(yùn)行spec_001_modul_test模塊中用例名稱包含spec的用例
pytest.main("-v -s -k spec spec_001_modul_test.py")
#運(yùn)行當(dāng)前文件夾匹配Test_Class的用例,類文件下面的用例
pytest.main('-s -v -k Test_Class')
7,按重要性級(jí)別進(jìn)行一定范圍測(cè)試
此標(biāo)記用來標(biāo)識(shí)測(cè)試用例或者測(cè)試類的級(jí)別,分為blocker,critical,normal,minor,trivial5個(gè)級(jí)別,下面?zhèn)儼褱y(cè)試用例按級(jí)別標(biāo)記,并查看一下測(cè)試報(bào)告
8, 為測(cè)試添加詳說明@allure.description;@allure.title;
1.title case標(biāo)題
可以自定義用例標(biāo)題,標(biāo)題默認(rèn)為函數(shù)名.
@allure.title
# -*- coding: utf-8 -*-
# @Time : 2019/3/12 11:46
# @Author : zzt
import allure
import pytest
@allure.title("用例標(biāo)題0")
def test_0():
pass
@allure.title("用例標(biāo)題1")
def test_1():
pass
def test_2():
pass
執(zhí)行效果:
- 說明
可以添加測(cè)試的詳細(xì)說明,以便根據(jù)需要為報(bào)告閱讀器提供盡可能多的上下文。
兩種方式:
@allure.description 提供描述字符串的裝飾器
@allure.description_html 提供一些HTML在測(cè)試用例的描述部分 (待研究)
# -*- coding: utf-8 -*-
# @Time : 2019/3/12 11:46
# @Author : zzt
import allure
import pytest
@allure.title("用例標(biāo)題0")
@allure.description("這里是對(duì)test_0用例的一些詳細(xì)說明")
def test_0():
pass
@allure.title("用例標(biāo)題1")
def test_1():
pass
@allure.title("用例標(biāo)題2")
def test_2():
pass
9, 鏈接@allure.link @allure.issue @allure.testcase
@allure.link @allure.issue @allure.testcase
# -*- coding: utf-8 -*-
# @Time : 2019/3/12 11:46
# @Author : zzt
import allure
import pytest
@allure.feature('這里是一級(jí)標(biāo)簽')
class TestAllure():
@allure.title("用例標(biāo)題0")
@allure.story("這里是第一個(gè)二級(jí)標(biāo)簽")
@pytest.mark.parametrize('param', ['青銅', '白銀', '黃金'])
def test_0(self, param):
allure.attach('附件內(nèi)容是: '+param, '我是附件名', allure.attachment_type.TEXT)
@allure.title("用例標(biāo)題1")
@allure.story("這里是第二個(gè)二級(jí)標(biāo)簽")
def test_1(self):
allure.attach.file(r'E:\Myproject\pytest-allure\test\test_1.jpg', '我是附件截圖的名字', attachment_type=allure.attachment_type.JPG)
@allure.title("用例標(biāo)題2")
@allure.story("這里是第三個(gè)二級(jí)標(biāo)簽")
@allure.issue('http://baidu.com', name='點(diǎn)擊我跳轉(zhuǎn)百度')
@allure.testcase('http://bug.com/user-login-Lw==.html', name='點(diǎn)擊我跳轉(zhuǎn)禪道')
def test_2(self):
pass
執(zhí)行結(jié)果如下:
參考鏈接
https://blog.csdn.net/qq_42610167/article/details/101204066