亚洲精品久久久中文字幕-亚洲精品久久片久久-亚洲精品久久青草-亚洲精品久久婷婷爱久久婷婷-亚洲精品久久午夜香蕉

您的位置:首頁(yè)技術(shù)文章
文章詳情頁(yè)

Python+unittest+requests 接口自動(dòng)化測(cè)試框架搭建教程

瀏覽:2日期:2022-07-09 08:10:16

一、Python+unittest+requests+HTMLTestRunner 完整的接口自動(dòng)化測(cè)試框架搭建_00——框架結(jié)構(gòu)簡(jiǎn)解

首先配置好開(kāi)發(fā)環(huán)境,下載安裝Python并下載安裝pycharm,在pycharm中創(chuàng)建項(xiàng)目功能目錄。如果不會(huì)的可以百度Google一下,該內(nèi)容網(wǎng)上的講解還是比較多比較全的!

Python+unittest+requests 接口自動(dòng)化測(cè)試框架搭建教程

大家可以先簡(jiǎn)單了解下該項(xiàng)目的目錄結(jié)構(gòu)介紹,后面會(huì)針對(duì)每個(gè)文件有詳細(xì)注解和代碼。

common:

——configDb.py:這個(gè)文件主要編寫(xiě)數(shù)據(jù)庫(kù)連接池的相關(guān)內(nèi)容,本項(xiàng)目暫未考慮使用數(shù)據(jù)庫(kù)來(lái)存儲(chǔ)讀取數(shù)據(jù),此文件可忽略,或者不創(chuàng)建。本人是留著以后如果有相關(guān)操作時(shí),方便使用。

——configEmail.py:這個(gè)文件主要是配置發(fā)送郵件的主題、正文等,將測(cè)試報(bào)告發(fā)送并抄送到相關(guān)人郵箱的邏輯。

——configHttp.py:這個(gè)文件主要來(lái)通過(guò)get、post、put、delete等方法來(lái)進(jìn)行http請(qǐng)求,并拿到請(qǐng)求響應(yīng)。

——HTMLTestRunner.py:主要是生成測(cè)試報(bào)告相關(guān)

——Log.py:調(diào)用該類的方法,用來(lái)打印生成日志

result:

——logs:生成的日志文件

——report.html:生成的測(cè)試報(bào)告

testCase:

——test01case.py:讀取userCase.xlsx中的用例,使用unittest來(lái)進(jìn)行斷言校驗(yàn)

testFile/case:

——userCase.xlsx:對(duì)下面test_api.py接口服務(wù)里的接口,設(shè)計(jì)了三條簡(jiǎn)單的測(cè)試用例,如參數(shù)為null,參數(shù)不正確等

caselist.txt:配置將要執(zhí)行testCase目錄下的哪些用例文件,前加#代表不進(jìn)行執(zhí)行。當(dāng)項(xiàng)目過(guò)于龐大,用例足夠多的時(shí)候,我們可以通過(guò)這個(gè)開(kāi)關(guān),來(lái)確定本次執(zhí)行哪些接口的哪些用例。

config.ini:數(shù)據(jù)庫(kù)、郵箱、接口等的配置項(xiàng),用于方便的調(diào)用讀取。

getpathInfo.py:獲取項(xiàng)目絕對(duì)路徑

geturlParams.py:獲取接口的URL、參數(shù)、method等

readConfig.py:讀取配置文件的方法,并返回文件中內(nèi)容

readExcel.py:讀取Excel的方法

runAll.py:開(kāi)始執(zhí)行接口自動(dòng)化,項(xiàng)目工程部署完畢后直接運(yùn)行該文件即可

test_api.py:自己寫(xiě)的提供本地測(cè)試的接口服務(wù)

test_sql.py:測(cè)試數(shù)據(jù)庫(kù)連接池的文件,本次項(xiàng)目未用到數(shù)據(jù)庫(kù),可以忽略

二、Python+unittest+requests+HTMLTestRunner完整的接口自動(dòng)化測(cè)試框架搭建_01——測(cè)試接口服務(wù)

首先,我們想搭建一個(gè)接口自動(dòng)化測(cè)試框架,前提我們必須要有一個(gè)可支持測(cè)試的接口服務(wù)。有人可能會(huì)說(shuō),現(xiàn)在我們的環(huán)境不管測(cè)試環(huán)境,還是生產(chǎn)環(huán)境有現(xiàn)成的接口。但是,一般工作環(huán)境中的接口,不太滿足我們框架的各種條件。舉例如,接口a可能是get接口b可能又是post,等等等等。因此我決定自己寫(xiě)一個(gè)簡(jiǎn)單的接口!用于我們這個(gè)框架的測(cè)試!

按第一講的目錄創(chuàng)建好文件,打開(kāi)test_api.py,寫(xiě)入如下代碼

import flaskimport jsonfrom flask import request’’’flask: web框架,通過(guò)flask提供的裝飾器@server.route()將普通函數(shù)轉(zhuǎn)換為服’’’# 創(chuàng)建一個(gè)服務(wù),把當(dāng)前這個(gè)python文件當(dāng)做一個(gè)服務(wù)server = flask.Flask(__name__)# @server.route()可以將普通函數(shù)轉(zhuǎn)變?yōu)榉?wù) 登錄接口的路徑、請(qǐng)求方式@server.route(’/login’, methods=[’get’, ’post’])def login(): # 獲取通過(guò)url請(qǐng)求傳參的數(shù)據(jù) username = request.values.get(’name’) # 獲取url請(qǐng)求傳的密碼,明文 pwd = request.values.get(’pwd’) # 判斷用戶名、密碼都不為空 if username and pwd: if username == ’xiaoming’ and pwd == ’111’: resu = {’code’: 200, ’message’: ’登錄成功’} return json.dumps(resu, ensure_ascii=False) # 將字典轉(zhuǎn)換字符串 else: resu = {’code’: -1, ’message’: ’賬號(hào)密碼錯(cuò)誤’} return json.dumps(resu, ensure_ascii=False) else: resu = {’code’: 10001, ’message’: ’參數(shù)不能為空!’} return json.dumps(resu, ensure_ascii=False)if __name__ == ’__main__’: server.run(debug=True, port=8888, host=’127.0.0.1’)

執(zhí)行test_api.py,在瀏覽器中輸入http://127.0.0.1:8888/login?name=xiaoming&pwd=11199回車,驗(yàn)證我們的接口服務(wù)是否正常~

Python+unittest+requests 接口自動(dòng)化測(cè)試框架搭建教程

變更我們的參數(shù),查看不同的響應(yīng)結(jié)果確認(rèn)接口服務(wù)一切正常

Python+unittest+requests 接口自動(dòng)化測(cè)試框架搭建教程

Python+unittest+requests 接口自動(dòng)化測(cè)試框架搭建教程

三、Python+unittest+requests+HTMLTestRunner完整的接口自動(dòng)化測(cè)試框架搭建_02——配置文件讀取

在我們第二講中,我們已經(jīng)通過(guò)flask這個(gè)web框架創(chuàng)建好了我們用于測(cè)試的接口服務(wù),因此我們可以把這個(gè)接口抽出來(lái)一些參數(shù)放到配置文件,然后通過(guò)一個(gè)讀取配置文件的方法,方便后續(xù)的使用。同樣還有郵件的相關(guān)配置~

按第一講的目錄創(chuàng)建好config.ini文件,打開(kāi)該文件寫(xiě)入如下:

# -*- coding: utf-8 -*-[HTTP]scheme = httpbaseurl = 127.0.0.1port = 8888timeout = 10.0[EMAIL]on_off = on;subject = 接口自動(dòng)化測(cè)試報(bào)告app = Outlookaddressee = [email protected] = [email protected]

在HTTP中,協(xié)議http,baseURL,端口,超時(shí)時(shí)間。

在郵件中on_off是設(shè)置的一個(gè)開(kāi)關(guān),=on打開(kāi),發(fā)送郵件,=其他不發(fā)送郵件。subject郵件主題,addressee收件人,cc抄送人。

在我們編寫(xiě)readConfig.py文件前,我們先寫(xiě)一個(gè)獲取項(xiàng)目某路徑下某文件絕對(duì)路徑的一個(gè)方法。按第一講的目錄結(jié)構(gòu)創(chuàng)建好getpathInfo.py,打開(kāi)該文件

import osdef get_Path(): path = os.path.split(os.path.realpath(__file__))[0] return pathif __name__ == ’__main__’:# 執(zhí)行該文件,測(cè)試下是否OK print(’測(cè)試路徑是否OK,路徑為:’, get_Path())

填寫(xiě)如上代碼并執(zhí)行后,查看輸出結(jié)果,打印出了該項(xiàng)目的絕對(duì)路徑:

Python+unittest+requests 接口自動(dòng)化測(cè)試框架搭建教程

繼續(xù)往下走,同理,按第一講目錄創(chuàng)建好readConfig.py文件,打開(kāi)該文件,以后的章節(jié)不在累贅

import osimport configparserimport getpathInfo#引入我們自己的寫(xiě)的獲取路徑的類path = getpathInfo.get_Path()#調(diào)用實(shí)例化,還記得這個(gè)類返回的路徑為C:UserssonglihuiPycharmProjectsdkxinterfaceTestconfig_path = os.path.join(path, ’config.ini’)#這句話是在path路徑下再加一級(jí),最后變成C:UserssonglihuiPycharmProjectsdkxinterfaceTestconfig.iniconfig = configparser.ConfigParser()#調(diào)用外部的讀取配置文件的方法config.read(config_path, encoding=’utf-8’)class ReadConfig(): def get_http(self, name): value = config.get(’HTTP’, name) return value def get_email(self, name): value = config.get(’EMAIL’, name) return value def get_mysql(self, name):#寫(xiě)好,留以后備用。但是因?yàn)槲覀儧](méi)有對(duì)數(shù)據(jù)庫(kù)的操作,所以這個(gè)可以屏蔽掉 value = config.get(’DATABASE’, name) return valueif __name__ == ’__main__’:#測(cè)試一下,我們讀取配置文件的方法是否可用 print(’HTTP中的baseurl值為:’, ReadConfig().get_http(’baseurl’)) print(’EMAIL中的開(kāi)關(guān)on_off值為:’, ReadConfig().get_email(’on_off’))

執(zhí)行下readConfig.py,查看數(shù)據(jù)是否正確

Python+unittest+requests 接口自動(dòng)化測(cè)試框架搭建教程

一切OK

四、Python+unittest+requests+HTMLTestRunner完整的接口自動(dòng)化測(cè)試框架搭建_03——讀取Excel中的case

配置文件寫(xiě)好了,接口我們也有了,然后我們來(lái)根據(jù)我們的接口設(shè)計(jì)我們簡(jiǎn)單的幾條用例。首先在前兩講中我們寫(xiě)了一個(gè)我們測(cè)試的接口服務(wù),針對(duì)這個(gè)接口服務(wù)存在三種情況的校驗(yàn)。正確的用戶名和密碼,賬號(hào)密碼錯(cuò)誤和賬號(hào)密碼為空

Python+unittest+requests 接口自動(dòng)化測(cè)試框架搭建教程

Python+unittest+requests 接口自動(dòng)化測(cè)試框架搭建教程

Python+unittest+requests 接口自動(dòng)化測(cè)試框架搭建教程

我們根據(jù)上面的三種情況,將對(duì)這個(gè)接口的用例寫(xiě)在一個(gè)對(duì)應(yīng)的單獨(dú)文件中testFilecaseuserCase.xlsx ,userCase.xlsx內(nèi)容如下:

Python+unittest+requests 接口自動(dòng)化測(cè)試框架搭建教程

緊接著,我們有了用例設(shè)計(jì)的Excel了,我們要對(duì)這個(gè)Excel進(jìn)行數(shù)據(jù)的讀取操作,繼續(xù)往下,我們創(chuàng)建readExcel.py文件

import osimport getpathInfo# 自己定義的內(nèi)部類,該類返回項(xiàng)目的絕對(duì)路徑#調(diào)用讀Excel的第三方庫(kù)xlrdfrom xlrd import open_workbook# 拿到該項(xiàng)目所在的絕對(duì)路徑path = getpathInfo.get_Path()class readExcel(): def get_xls(self, xls_name, sheet_name):# xls_name填寫(xiě)用例的Excel名稱 sheet_name該Excel的sheet名稱 cls = [] # 獲取用例文件路徑 xlsPath = os.path.join(path, 'testFile', ’case’, xls_name) file = open_workbook(xlsPath)# 打開(kāi)用例Excel sheet = file.sheet_by_name(sheet_name)#獲得打開(kāi)Excel的sheet # 獲取這個(gè)sheet內(nèi)容行數(shù) nrows = sheet.nrows for i in range(nrows):#根據(jù)行數(shù)做循環(huán) if sheet.row_values(i)[0] != u’case_name’:#如果這個(gè)Excel的這個(gè)sheet的第i行的第一列不等于case_name那么我們把這行的數(shù)據(jù)添加到cls[]cls.append(sheet.row_values(i)) return clsif __name__ == ’__main__’:#我們執(zhí)行該文件測(cè)試一下是否可以正確獲取Excel中的值 print(readExcel().get_xls(’userCase.xlsx’, ’login’)) print(readExcel().get_xls(’userCase.xlsx’, ’login’)[0][1]) print(readExcel().get_xls(’userCase.xlsx’, ’login’)[1][2])

結(jié)果為:

Python+unittest+requests 接口自動(dòng)化測(cè)試框架搭建教程

完全正確~

五、Python+unittest+requests+HTMLTestRunner完整的接口自動(dòng)化測(cè)試框架搭建_04——requests請(qǐng)求

配置文件有了,讀取配置文件有了,用例有了,讀取用例有了,我們的接口服務(wù)有了,我們是不是該寫(xiě)對(duì)某個(gè)接口進(jìn)行http請(qǐng)求了,這時(shí)候我們需要使用pip install requests來(lái)安裝第三方庫(kù),在common下configHttp.py,configHttp.py的內(nèi)容如下:

import requestsimport jsonclass RunMain(): def send_post(self, url, data): # 定義一個(gè)方法,傳入需要的參數(shù)url和data # 參數(shù)必須按照url、data順序傳入 result = requests.post(url=url, data=data).json() # 因?yàn)檫@里要封裝post方法,所以這里的url和data值不能寫(xiě)死 res = json.dumps(result, ensure_ascii=False, sort_keys=True, indent=2) return res def send_get(self, url, data): result = requests.get(url=url, params=data).json() res = json.dumps(result, ensure_ascii=False, sort_keys=True, indent=2) return res def run_main(self, method, url=None, data=None): # 定義一個(gè)run_main函數(shù),通過(guò)傳過(guò)來(lái)的method來(lái)進(jìn)行不同的get或post請(qǐng)求 result = None if method == ’post’: result = self.send_post(url, data) elif method == ’get’: result = self.send_get(url, data) else: print('method值錯(cuò)誤!??!') return resultif __name__ == ’__main__’: # 通過(guò)寫(xiě)死參數(shù),來(lái)驗(yàn)證我們寫(xiě)的請(qǐng)求是否正確 result1 = RunMain().run_main(’post’, ’http://127.0.0.1:8888/login’, {’name’: ’xiaoming’,’pwd’:’111’}) result2 = RunMain().run_main(’get’, ’http://127.0.0.1:8888/login’, ’name=xiaoming&pwd=111’) print(result1) print(result2)

執(zhí)行該文件,驗(yàn)證結(jié)果正確性:

Python+unittest+requests 接口自動(dòng)化測(cè)試框架搭建教程

我們發(fā)現(xiàn)和瀏覽器中進(jìn)行請(qǐng)求該接口,得到的結(jié)果一致,說(shuō)明沒(méi)有問(wèn)題,一切OK

六、Python+unittest+requests+HTMLTestRunner完整的接口自動(dòng)化測(cè)試框架搭建_05——參數(shù)動(dòng)態(tài)化

在上一講中,我們寫(xiě)了針對(duì)我們的接口服務(wù),設(shè)計(jì)的三種測(cè)試用例,使用寫(xiě)死的參數(shù)(result = RunMain().run_main(’post’, ’http://127.0.0.1:8888/login’, ’name=xiaoming&pwd=’))來(lái)進(jìn)行requests請(qǐng)求。本講中我們寫(xiě)一個(gè)類,來(lái)用于分別獲取這些參數(shù),來(lái)第一講的目錄創(chuàng)建geturlParams.py,geturlParams.py文件中的內(nèi)容如下:

import readConfig as readConfigreadconfig = readConfig.ReadConfig()class geturlParams():# 定義一個(gè)方法,將從配置文件中讀取的進(jìn)行拼接 def get_Url(self): new_url = readconfig.get_http(’scheme’) + ’://’ + readconfig.get_http(’baseurl’) + ’:8888’ + ’/login’ + ’?’ #logger.info(’new_url’+new_url) return new_urlif __name__ == ’__main__’:# 驗(yàn)證拼接后的正確性 print(geturlParams().get_Url())

通過(guò)將配置文件中的進(jìn)行拼接,拼接后的結(jié)果:http://127.0.0.1:8888/login?和我們請(qǐng)求的一致

Python+unittest+requests 接口自動(dòng)化測(cè)試框架搭建教程

七、Python+unittest+requests+HTMLTestRunner完整的接口自動(dòng)化測(cè)試框架搭建_06——unittest斷言

以上的我們都準(zhǔn)備好了,剩下的該寫(xiě)我們的unittest斷言測(cè)試case了,在testCase下創(chuàng)建test01case.py文件,文件中內(nèi)容如下:

import jsonimport unittestfrom common.configHttp import RunMainimport paramunittestimport geturlParamsimport urllib.parse# import pythoncomimport readExcel# pythoncom.CoInitialize()url = geturlParams.geturlParams().get_Url()# 調(diào)用我們的geturlParams獲取我們拼接的URLlogin_xls = readExcel.readExcel().get_xls(’userCase.xlsx’, ’login’)@paramunittest.parametrized(*login_xls)class testUserLogin(unittest.TestCase): def setParameters(self, case_name, path, query, method): ''' set params :param case_name: :param path :param query :param method :return: ''' self.case_name = str(case_name) self.path = str(path) self.query = str(query) self.method = str(method) def description(self): ''' test report description :return: ''' self.case_name def setUp(self): ''' :return: ''' print(self.case_name+'測(cè)試開(kāi)始前準(zhǔn)備') def test01case(self): self.checkResult() def tearDown(self): print('測(cè)試結(jié)束,輸出log完結(jié)nn') def checkResult(self):# 斷言 ''' check test result :return: ''' url1 = 'http://www.xxx.com/login?' new_url = url1 + self.query data1 = dict(urllib.parse.parse_qsl(urllib.parse.urlsplit(new_url).query))# 將一個(gè)完整的URL中的name=&pwd=轉(zhuǎn)換為{’name’:’xxx’,’pwd’:’bbb’} info = RunMain().run_main(self.method, url, data1)# 根據(jù)Excel中的method調(diào)用run_main來(lái)進(jìn)行requests請(qǐng)求,并拿到響應(yīng) ss = json.loads(info)# 將響應(yīng)轉(zhuǎn)換為字典格式 if self.case_name == ’login’:# 如果case_name是login,說(shuō)明合法,返回的code應(yīng)該為200 self.assertEqual(ss[’code’], 200) if self.case_name == ’login_error’:# 同上 self.assertEqual(ss[’code’], -1) if self.case_name == ’login_null’:# 同上 self.assertEqual(ss[’code’], 10001)

八、Python+unittest+requests+HTMLTestRunner完整的接口自動(dòng)化測(cè)試框架搭建_07——HTMLTestRunner

按我的目錄結(jié)構(gòu),在common下創(chuàng)建HTMLTestRunner.py文件,內(nèi)容如下:

# -*- coding: utf-8 -*-'''A TestRunner for use with the Python unit testing framework. Itgenerates a HTML report to show the result at a glance.The simplest way to use this is to invoke its main method. E.g. import unittest import HTMLTestRunner ... define your tests ... if __name__ == ’__main__’: HTMLTestRunner.main()For more customization options, instantiates a HTMLTestRunner object.HTMLTestRunner is a counterpart to unittest’s TextTestRunner. E.g. # output to a file fp = file(’my_report.html’, ’wb’) runner = HTMLTestRunner.HTMLTestRunner(stream=fp,title=’My unit test’,description=’This demonstrates the report output by HTMLTestRunner.’) # Use an external stylesheet. # See the Template_mixin class for more customizable options runner.STYLESHEET_TMPL = ’<link rel='stylesheet' href='http://www.aoyou183.cn/bcjs/my_stylesheet.css' rel='external nofollow' type='text/css'>’ # run the test runner.run(my_test_suite)------------------------------------------------------------------------Copyright (c) 2004-2007, Wai Yip TungAll rights reserved.Redistribution and use in source and binary forms, with or withoutmodification, are permitted provided that the following conditions aremet:* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.* Neither the name Wai Yip Tung nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'ASIS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITEDTO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR APARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNEROR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, ORPROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OFLIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDINGNEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THISSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.'''# URL: http://tungwaiyip.info/software/HTMLTestRunner.html__author__ = 'Wai Yip Tung'__version__ = '0.9.1''''Change HistoryVersion 0.9.1* 用Echarts添加執(zhí)行情況統(tǒng)計(jì)圖 (灰藍(lán))Version 0.9.0* 改成Python 3.x (灰藍(lán))Version 0.8.3* 使用 Bootstrap稍加美化 (灰藍(lán))* 改為中文 (灰藍(lán))Version 0.8.2* Show output inline instead of popup window (Viorel Lupu).Version in 0.8.1* Validated XHTML (Wolfgang Borgert).* Added description of test classes and test cases.Version in 0.8.0* Define Template_mixin class for customization.* Workaround a IE 6 bug that it does not treat <script> block as CDATA.Version in 0.7.1* Back port to Python 2.3 (Frank Horowitz).* Fix missing scroll bars in detail log (Podi).'''# TODO: color stderr# TODO: simplify javascript using ,ore than 1 class in the class attribute?import datetimeimport sysimport ioimport timeimport unittestfrom xml.sax import saxutils# ------------------------------------------------------------------------# The redirectors below are used to capture output during testing. Output# sent to sys.stdout and sys.stderr are automatically captured. However# in some cases sys.stdout is already cached before HTMLTestRunner is# invoked (e.g. calling logging.basicConfig). In order to capture those# output, use the redirectors for the cached stream.## e.g.# >>> logging.basicConfig(stream=HTMLTestRunner.stdout_redirector)# >>>class OutputRedirector(object): ''' Wrapper to redirect stdout or stderr ''' def __init__(self, fp): self.fp = fp def write(self, s): self.fp.write(s) def writelines(self, lines): self.fp.writelines(lines) def flush(self): self.fp.flush()stdout_redirector = OutputRedirector(sys.stdout)stderr_redirector = OutputRedirector(sys.stderr)# ----------------------------------------------------------------------# Templateclass Template_mixin(object): ''' Define a HTML template for report customerization and generation. Overall structure of an HTML report HTML +------------------------+ |<html> | | <head>| | | | STYLESHEET | | +----------------+ | | || | | +----------------+ | | | | </head>| | | | <body>| | | | HEADING | | +----------------+ | | || | | +----------------+ | | | | REPORT| | +----------------+ | | || | | +----------------+ | | | | ENDING| | +----------------+ | | || | | +----------------+ | | | | </body>| |</html> | +------------------------+ ''' STATUS = { 0: u’通過(guò)’, 1: u’失敗’, 2: u’錯(cuò)誤’, } DEFAULT_TITLE = ’Unit Test Report’ DEFAULT_DESCRIPTION = ’’ # ------------------------------------------------------------------------ # HTML Template HTML_TMPL = r'''<?xml version='1.0' encoding='UTF-8'?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Strict//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'><html xmlns='http://www.w3.org/1999/xhtml'><head> <title>%(title)s</title> <meta name='generator' content='%(generator)s'/> <meta http-equiv='Content-Type' content='text/html; charset=UTF-8'/> <link rel='external nofollow' rel='stylesheet'> <script src='https://cdn.bootcss.com/echarts/3.8.5/echarts.common.min.js'></script> <!-- <script type='text/javascript' src='http://www.aoyou183.cn/bcjs/js/echarts.common.min.js'></script> --> %(stylesheet)s</head><body> <script language='javascript' type='text/javascript'><!-- output_list = Array(); /* level - 0:Summary; 1:Failed; 2:All */ function showCase(level) { trs = document.getElementsByTagName('tr'); for (var i = 0; i < trs.length; i++) { tr = trs[i]; id = tr.id; if (id.substr(0,2) == ’ft’) {if (level < 1) { tr.className = ’hiddenRow’;}else { tr.className = ’’;} } if (id.substr(0,2) == ’pt’) {if (level > 1) { tr.className = ’’;}else { tr.className = ’hiddenRow’;} } } } function showClassDetail(cid, count) { var id_list = Array(count); var toHide = 1; for (var i = 0; i < count; i++) { tid0 = ’t’ + cid.substr(1) + ’.’ + (i+1); tid = ’f’ + tid0; tr = document.getElementById(tid); if (!tr) {tid = ’p’ + tid0;tr = document.getElementById(tid); } id_list[i] = tid; if (tr.className) {toHide = 0; } } for (var i = 0; i < count; i++) { tid = id_list[i]; if (toHide) {document.getElementById(’div_’+tid).style.display = ’none’document.getElementById(tid).className = ’hiddenRow’; } else {document.getElementById(tid).className = ’’; } } } function showTestDetail(div_id){ var details_div = document.getElementById(div_id) var displayState = details_div.style.display // alert(displayState) if (displayState != ’block’ ) { displayState = ’block’ details_div.style.display = ’block’ } else { details_div.style.display = ’none’ } } function html_escape(s) { s = s.replace(/&/g,’&amp;’); s = s.replace(/</g,’&lt;’); s = s.replace(/>/g,’&gt;’); return s; } /* obsoleted by detail in <div> function showOutput(id, name) { var w = window.open('', //url name, 'resizable,scrollbars,status,width=800,height=450'); d = w.document; d.write('<pre>'); d.write(html_escape(output_list[id])); d.write('n'); d.write('<a href=’javascript:window.close()’>close</a>n'); d.write('</pre>n'); d.close(); } */ --></script> <div id='div_base'> %(heading)s %(report)s %(ending)s %(chart_script)s </div></body></html>''' # variables: (title, generator, stylesheet, heading, report, ending, chart_script) ECHARTS_SCRIPT = ''' <script type='text/javascript'> // 基于準(zhǔn)備好的dom,初始化echarts實(shí)例 var myChart = echarts.init(document.getElementById(’chart’)); // 指定圖表的配置項(xiàng)和數(shù)據(jù) var option = { title : {text: ’測(cè)試執(zhí)行情況’,x:’center’ }, tooltip : {trigger: ’item’,formatter: '{a} <br/> : {c} (wyw2q2k%%)' }, color: [’#95b75d’, ’grey’, ’#b64645’], legend: {orient: ’vertical’,left: ’left’,data: [’通過(guò)’,’失敗’,’錯(cuò)誤’] }, series : [{ name: ’測(cè)試執(zhí)行情況’, type: ’pie’, radius : ’60%%’, center: [’50%%’, ’60%%’], data:[ {value:%(Pass)s, name:’通過(guò)’}, {value:%(fail)s, name:’失敗’}, {value:%(error)s, name:’錯(cuò)誤’} ], itemStyle: { emphasis: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: ’rgba(0, 0, 0, 0.5)’ } }} ] }; // 使用剛指定的配置項(xiàng)和數(shù)據(jù)顯示圖表。 myChart.setOption(option); </script> ''' # variables: (Pass, fail, error) # ------------------------------------------------------------------------ # Stylesheet # # alternatively use a <link> for external style sheet, e.g. # <link rel='stylesheet' href='http://www.aoyou183.cn/bcjs/$url' rel='external nofollow' type='text/css'> STYLESHEET_TMPL = '''<style type='text/css' media='screen'> body { font-family: Microsoft YaHei,Consolas,arial,sans-serif; font-size: 80%; } table { font-size: 100%; } pre { white-space: pre-wrap;word-wrap: break-word; } /* -- heading ---------------------------------------------------------------------- */ h1 { font-size: 16pt; color: gray; } .heading { margin-top: 0ex; margin-bottom: 1ex; } .heading .attribute { margin-top: 1ex; margin-bottom: 0; } .heading .description { margin-top: 2ex; margin-bottom: 3ex; } /* -- css div popup ------------------------------------------------------------------------ */ a.popup_link { } a.popup_link:hover { color: red; } .popup_window { display: none; position: relative; left: 0px; top: 0px; /*border: solid #627173 1px; */ padding: 10px; /*background-color: #E6E6D6; */ font-family: 'Lucida Console', 'Courier New', Courier, monospace; text-align: left; font-size: 8pt; /* width: 500px;*/ } } /* -- report ------------------------------------------------------------------------ */ #show_detail_line { margin-top: 3ex; margin-bottom: 1ex; } #result_table { width: 99%; } #header_row { font-weight: bold; color: #303641; background-color: #ebebeb; } #total_row { font-weight: bold; } .passClass { background-color: #bdedbc; } .failClass { background-color: #ffefa4; } .errorClass { background-color: #ffc9c9; } .passCase { color: #6c6; } .failCase { color: #FF6600; font-weight: bold; } .errorCase { color: #c00; font-weight: bold; } .hiddenRow { display: none; } .testcase { margin-left: 2em; } /* -- ending ---------------------------------------------------------------------- */ #ending { } #div_base {position:absolute;top:0%;left:5%;right:5%;width: auto;height: auto;margin: -15px 0 0 0; }</style>''' # ------------------------------------------------------------------------ # Heading # HEADING_TMPL = ''' <div class=’page-header’> <h1>%(title)s</h1> %(parameters)s </div> <div style='float: left;width:50%%;'><p class=’description’>%(description)s</p></div> <div style='width:50%%;height:400px;float:left;'></div>''' # variables: (title, parameters, description) HEADING_ATTRIBUTE_TMPL = '''<p class=’attribute’><strong>%(name)s:</strong> %(value)s</p>''' # variables: (name, value) # ------------------------------------------------------------------------ # Report # REPORT_TMPL = u''' <div class='btn-group btn-group-sm'> <button onclick=’javascript:showCase(0)’>總結(jié)</button> <button onclick=’javascript:showCase(1)’>失敗</button> <button onclick=’javascript:showCase(2)’>全部</button> </div> <p></p> <table id=’result_table’ class='table table-bordered'> <colgroup> <col align=’left’ /> <col align=’right’ /> <col align=’right’ /> <col align=’right’ /> <col align=’right’ /> <col align=’right’ /> </colgroup> <tr id=’header_row’> <td>測(cè)試套件/測(cè)試用例</td> <td>總數(shù)</td> <td>通過(guò)</td> <td>失敗</td> <td>錯(cuò)誤</td> <td>查看</td> </tr> %(test_list)s <tr id=’total_row’> <td>總計(jì)</td> <td>%(count)s</td> <td>%(Pass)s</td> <td>%(fail)s</td> <td>%(error)s</td> <td>&nbsp;</td> </tr> </table>''' # variables: (test_list, count, Pass, fail, error) REPORT_CLASS_TMPL = u''' <tr class=’%(style)s’> <td>%(desc)s</td> <td>%(count)s</td> <td>%(Pass)s</td> <td>%(fail)s</td> <td>%(error)s</td> <td><a href='javascript:showClassDetail(’%(cid)s’,%(count)s)' rel='external nofollow' >詳情</a></td> </tr>''' # variables: (style, desc, count, Pass, fail, error, cid) REPORT_TEST_WITH_OUTPUT_TMPL = r'''<tr id=’%(tid)s’ class=’%(Class)s’> <td class=’%(style)s’><div class=’testcase’>%(desc)s</div></td> <td colspan=’5’ align=’center’> <!--css div popup start--> <a onfocus=’this.blur();’ href='javascript:showTestDetail(’div_%(tid)s’)' rel='external nofollow' > %(status)s</a> <div id=’div_%(tid)s’ class='popup_window'> <pre>%(script)s</pre> </div> <!--css div popup end--> </td></tr>''' # variables: (tid, Class, style, desc, status) REPORT_TEST_NO_OUTPUT_TMPL = r'''<tr id=’%(tid)s’ class=’%(Class)s’> <td class=’%(style)s’><div class=’testcase’>%(desc)s</div></td> <td colspan=’5’ align=’center’>%(status)s</td></tr>''' # variables: (tid, Class, style, desc, status) REPORT_TEST_OUTPUT_TMPL = r'''%(id)s: %(output)s''' # variables: (id, output) # ------------------------------------------------------------------------ # ENDING # ENDING_TMPL = '''<div id=’ending’>&nbsp;</div>'''# -------------------- The end of the Template class -------------------TestResult = unittest.TestResultclass _TestResult(TestResult): # note: _TestResult is a pure representation of results. # It lacks the output and reporting ability compares to unittest._TextTestResult. def __init__(self, verbosity=1): TestResult.__init__(self) self.stdout0 = None self.stderr0 = None self.success_count = 0 self.failure_count = 0 self.error_count = 0 self.verbosity = verbosity # result is a list of result in 4 tuple # ( # result code (0: success; 1: fail; 2: error), # TestCase object, # Test output (byte string), # stack trace, # ) self.result = [] self.subtestlist = [] def startTest(self, test): TestResult.startTest(self, test) # just one buffer for both stdout and stderr self.outputBuffer = io.StringIO() stdout_redirector.fp = self.outputBuffer stderr_redirector.fp = self.outputBuffer self.stdout0 = sys.stdout self.stderr0 = sys.stderr sys.stdout = stdout_redirector sys.stderr = stderr_redirector def complete_output(self): ''' Disconnect output redirection and return buffer. Safe to call multiple times. ''' if self.stdout0: sys.stdout = self.stdout0 sys.stderr = self.stderr0 self.stdout0 = None self.stderr0 = None return self.outputBuffer.getvalue() def stopTest(self, test): # Usually one of addSuccess, addError or addFailure would have been called. # But there are some path in unittest that would bypass this. # We must disconnect stdout in stopTest(), which is guaranteed to be called. self.complete_output() def addSuccess(self, test): if test not in self.subtestlist: self.success_count += 1 TestResult.addSuccess(self, test) output = self.complete_output() self.result.append((0, test, output, ’’)) if self.verbosity > 1:sys.stderr.write(’ok ’)sys.stderr.write(str(test))sys.stderr.write(’n’) else:sys.stderr.write(’.’) def addError(self, test, err): self.error_count += 1 TestResult.addError(self, test, err) _, _exc_str = self.errors[-1] output = self.complete_output() self.result.append((2, test, output, _exc_str)) if self.verbosity > 1: sys.stderr.write(’E ’) sys.stderr.write(str(test)) sys.stderr.write(’n’) else: sys.stderr.write(’E’) def addFailure(self, test, err): self.failure_count += 1 TestResult.addFailure(self, test, err) _, _exc_str = self.failures[-1] output = self.complete_output() self.result.append((1, test, output, _exc_str)) if self.verbosity > 1: sys.stderr.write(’F ’) sys.stderr.write(str(test)) sys.stderr.write(’n’) else: sys.stderr.write(’F’) def addSubTest(self, test, subtest, err): if err is not None: if getattr(self, ’failfast’, False):self.stop() if issubclass(err[0], test.failureException):self.failure_count += 1errors = self.failureserrors.append((subtest, self._exc_info_to_string(err, subtest)))output = self.complete_output()self.result.append((1, test, output + ’nSubTestCase Failed:n’ + str(subtest), self._exc_info_to_string(err, subtest)))if self.verbosity > 1: sys.stderr.write(’F ’) sys.stderr.write(str(subtest)) sys.stderr.write(’n’)else: sys.stderr.write(’F’) else:self.error_count += 1errors = self.errorserrors.append((subtest, self._exc_info_to_string(err, subtest)))output = self.complete_output()self.result.append( (2, test, output + ’nSubTestCase Error:n’ + str(subtest), self._exc_info_to_string(err, subtest)))if self.verbosity > 1: sys.stderr.write(’E ’) sys.stderr.write(str(subtest)) sys.stderr.write(’n’)else: sys.stderr.write(’E’) self._mirrorOutput = True else: self.subtestlist.append(subtest) self.subtestlist.append(test) self.success_count += 1 output = self.complete_output() self.result.append((0, test, output + ’nSubTestCase Pass:n’ + str(subtest), ’’)) if self.verbosity > 1:sys.stderr.write(’ok ’)sys.stderr.write(str(subtest))sys.stderr.write(’n’) else:sys.stderr.write(’.’)class HTMLTestRunner(Template_mixin): def __init__(self, stream=sys.stdout, verbosity=1, title=None, description=None): self.stream = stream self.verbosity = verbosity if title is None: self.title = self.DEFAULT_TITLE else: self.title = title if description is None: self.description = self.DEFAULT_DESCRIPTION else: self.description = description self.startTime = datetime.datetime.now() def run(self, test): 'Run the given test case or test suite.' result = _TestResult(self.verbosity) test(result) self.stopTime = datetime.datetime.now() self.generateReport(test, result) print(’nTime Elapsed: %s’ % (self.stopTime - self.startTime), file=sys.stderr) return result def sortResult(self, result_list): # unittest does not seems to run in any particular order. # Here at least we want to group them together by class. rmap = {} classes = [] for n, t, o, e in result_list: cls = t.__class__ if cls not in rmap:rmap[cls] = []classes.append(cls) rmap[cls].append((n, t, o, e)) r = [(cls, rmap[cls]) for cls in classes] return r def getReportAttributes(self, result): ''' Return report attributes as a list of (name, value). Override this to add custom attributes. ''' startTime = str(self.startTime)[:19] duration = str(self.stopTime - self.startTime) status = [] if result.success_count: status.append(u’通過(guò) %s’ % result.success_count) if result.failure_count: status.append(u’失敗 %s’ % result.failure_count) if result.error_count: status.append(u’錯(cuò)誤 %s’ % result.error_count) if status: status = ’ ’.join(status) else: status = ’none’ return [ (u’開(kāi)始時(shí)間’, startTime), (u’運(yùn)行時(shí)長(zhǎng)’, duration), (u’狀態(tài)’, status), ] def generateReport(self, test, result): report_attrs = self.getReportAttributes(result) generator = ’HTMLTestRunner %s’ % __version__ stylesheet = self._generate_stylesheet() heading = self._generate_heading(report_attrs) report = self._generate_report(result) ending = self._generate_ending() chart = self._generate_chart(result) output = self.HTML_TMPL % dict( title=saxutils.escape(self.title), generator=generator, stylesheet=stylesheet, heading=heading, report=report, ending=ending, chart_script=chart ) self.stream.write(output.encode(’utf8’)) def _generate_stylesheet(self): return self.STYLESHEET_TMPL def _generate_heading(self, report_attrs): a_lines = [] for name, value in report_attrs: line = self.HEADING_ATTRIBUTE_TMPL % dict(name=saxutils.escape(name),value=saxutils.escape(value), ) a_lines.append(line) heading = self.HEADING_TMPL % dict( title=saxutils.escape(self.title), parameters=’’.join(a_lines), description=saxutils.escape(self.description), ) return heading def _generate_report(self, result): rows = [] sortedResult = self.sortResult(result.result) for cid, (cls, cls_results) in enumerate(sortedResult): # subtotal for a class np = nf = ne = 0 for n, t, o, e in cls_results:if n == 0: np += 1elif n == 1: nf += 1else: ne += 1 # format class description if cls.__module__ == '__main__':name = cls.__name__ else:name = '%s.%s' % (cls.__module__, cls.__name__) doc = cls.__doc__ and cls.__doc__.split('n')[0] or '' desc = doc and ’%s: %s’ % (name, doc) or name row = self.REPORT_CLASS_TMPL % dict(style=ne > 0 and ’errorClass’ or nf > 0 and ’failClass’ or ’passClass’,desc=desc,count=np + nf + ne,Pass=np,fail=nf,error=ne,cid=’c%s’ % (cid + 1), ) rows.append(row) for tid, (n, t, o, e) in enumerate(cls_results):self._generate_report_test(rows, cid, tid, n, t, o, e) report = self.REPORT_TMPL % dict( test_list=’’.join(rows), count=str(result.success_count + result.failure_count + result.error_count), Pass=str(result.success_count), fail=str(result.failure_count), error=str(result.error_count), ) return report def _generate_chart(self, result): chart = self.ECHARTS_SCRIPT % dict( Pass=str(result.success_count), fail=str(result.failure_count), error=str(result.error_count), ) return chart def _generate_report_test(self, rows, cid, tid, n, t, o, e): # e.g. ’pt1.1’, ’ft1.1’, etc has_output = bool(o or e) tid = (n == 0 and ’p’ or ’f’) + ’t%s.%s’ % (cid + 1, tid + 1) name = t.id().split(’.’)[-1] doc = t.shortDescription() or '' desc = doc and (’%s: %s’ % (name, doc)) or name tmpl = has_output and self.REPORT_TEST_WITH_OUTPUT_TMPL or self.REPORT_TEST_NO_OUTPUT_TMPL script = self.REPORT_TEST_OUTPUT_TMPL % dict( id=tid, output=saxutils.escape(o + e), ) row = tmpl % dict( tid=tid, Class=(n == 0 and ’hiddenRow’ or ’none’), style=(n == 2 and ’errorCase’ or (n == 1 and ’failCase’ or ’none’)), desc=desc, script=script, status=self.STATUS[n], ) rows.append(row) if not has_output: return def _generate_ending(self): return self.ENDING_TMPL############################################################################### Facilities for running tests from the command line############################################################################### Note: Reuse unittest.TestProgram to launch test. In the future we may# build our own launcher to support more specific command line# parameters like test title, CSS, etc.class TestProgram(unittest.TestProgram): ''' A variation of the unittest.TestProgram. Please refer to the base class for command line parameters. ''' def runTests(self): # Pick HTMLTestRunner as the default test runner. # base class’s testRunner parameter is not useful because it means # we have to instantiate HTMLTestRunner before we know self.verbosity. if self.testRunner is None: self.testRunner = HTMLTestRunner(verbosity=self.verbosity) unittest.TestProgram.runTests(self)main = TestProgram############################################################################### Executing this module from the command line##############################################################################if __name__ == '__main__': main(module=None)

九、Python+unittest+requests+HTMLTestRunner完整的接口自動(dòng)化測(cè)試框架搭建_08——調(diào)用生成測(cè)試報(bào)告

先別急著創(chuàng)建runAll.py文件(所有工作做完,最后我們運(yùn)行runAll.py文件來(lái)執(zhí)行接口自動(dòng)化的測(cè)試工作并生成測(cè)試報(bào)告發(fā)送報(bào)告到相關(guān)人郵箱),但是我們?cè)趧?chuàng)建此文件前,還缺少點(diǎn)東東。按我的目錄結(jié)構(gòu)創(chuàng)建caselist.txt文件,內(nèi)容如下:

user/test01case#user/test02case#user/test03case#user/test04case#user/test05case#shop/test_shop_list#shop/test_my_shop#shop/test_new_shop

這個(gè)文件的作用是,我們通過(guò)這個(gè)文件來(lái)控制,執(zhí)行哪些模塊下的哪些unittest用例文件。如在實(shí)際的項(xiàng)目中:user模塊下的test01case.py,店鋪shop模塊下的我的店鋪my_shop,如果本輪無(wú)需執(zhí)行哪些模塊的用例的話,就在前面添加#。我們繼續(xù)往下走,還缺少一個(gè)發(fā)送郵件的文件。在common下創(chuàng)建configEmail.py文件,內(nèi)容如下:

# import os# import win32com.client as win32# import datetime# import readConfig# import getpathInfo# # # read_conf = readConfig.ReadConfig()# subject = read_conf.get_email(’subject’)#從配置文件中讀取,郵件主題# app = str(read_conf.get_email(’app’))#從配置文件中讀取,郵件類型# addressee = read_conf.get_email(’addressee’)#從配置文件中讀取,郵件收件人# cc = read_conf.get_email(’cc’)#從配置文件中讀取,郵件抄送人# mail_path = os.path.join(getpathInfo.get_Path(), ’result’, ’report.html’)#獲取測(cè)試報(bào)告路徑# # class send_email():# def outlook(self):# olook = win32.Dispatch('%s.Application' % app)# mail = olook.CreateItem(win32.constants.olMailItem)# mail.To = addressee # 收件人# mail.CC = cc # 抄送# mail.Subject = str(datetime.datetime.now())[0:19]+’%s’ %subject#郵件主題# mail.Attachments.Add(mail_path, 1, 1, 'myFile')# content = '''# 執(zhí)行測(cè)試中……# 測(cè)試已完成!!# 生成報(bào)告中……# 報(bào)告已生成……# 報(bào)告已郵件發(fā)送!!# '''# mail.Body = content# mail.Send()# # # if __name__ == ’__main__’:# 運(yùn)營(yíng)此文件來(lái)驗(yàn)證寫(xiě)的send_email是否正確# print(subject)# send_email().outlook()# print('send email ok!!!!!!!!!!')# 兩種方式,第一種是用的win32com,因?yàn)橄到y(tǒng)等各方面原因,反饋win32問(wèn)題較多,建議改成下面的smtplib方式import osimport smtplibimport base64from email.mime.text import MIMETextfrom email.mime.multipart import MIMEMultipartclass SendEmail(object): def __init__(self, username, passwd, recv, title, content, file=None, ssl=False, email_host=’smtp.163.com’, port=25, ssl_port=465): self.username = username # 用戶名 self.passwd = passwd # 密碼 self.recv = recv # 收件人,多個(gè)要傳list [’[email protected]’,’[email protected]] self.title = title # 郵件標(biāo)題 self.content = content # 郵件正文 self.file = file # 附件路徑,如果不在當(dāng)前目錄下,要寫(xiě)絕對(duì)路徑 self.email_host = email_host # smtp服務(wù)器地址 self.port = port # 普通端口 self.ssl = ssl # 是否安全鏈接 self.ssl_port = ssl_port # 安全鏈接端口 def send_email(self): msg = MIMEMultipart() # 發(fā)送內(nèi)容的對(duì)象 if self.file: # 處理附件的 file_name = os.path.split(self.file)[-1] # 只取文件名,不取路徑 try:f = open(self.file, ’rb’).read() except Exception as e:raise Exception(’附件打不開(kāi)!!??!’) else:att = MIMEText(f, 'base64', 'utf-8')att['Content-Type'] = ’application/octet-stream’# base64.b64encode(file_name.encode()).decode()new_file_name = ’=?utf-8?b?’ + base64.b64encode(file_name.encode()).decode() + ’?=’# 這里是處理文件名為中文名的,必須這么寫(xiě)att['Content-Disposition'] = ’attachment; filename='%s'’ % (new_file_name)msg.attach(att) msg.attach(MIMEText(self.content)) # 郵件正文的內(nèi)容 msg[’Subject’] = self.title # 郵件主題 msg[’From’] = self.username # 發(fā)送者賬號(hào) msg[’To’] = ’,’.join(self.recv) # 接收者賬號(hào)列表 if self.ssl: self.smtp = smtplib.SMTP_SSL(self.email_host, port=self.ssl_port) else: self.smtp = smtplib.SMTP(self.email_host, port=self.port) # 發(fā)送郵件服務(wù)器的對(duì)象 self.smtp.login(self.username, self.passwd) try: self.smtp.sendmail(self.username, self.recv, msg.as_string()) pass except Exception as e: print(’出錯(cuò)了。?!? e) else: print(’發(fā)送成功!’) self.smtp.quit()if __name__ == ’__main__’: m = SendEmail( username=’@163.com’, passwd=’’, recv=[’’], title=’’, content=’測(cè)試發(fā)送郵件’, file=r’E:test_recordv2.3.3測(cè)試截圖調(diào)整樣式.png’, ssl=True, ) m.send_email()

運(yùn)行configEmail.py驗(yàn)證郵件發(fā)送是否正確

Python+unittest+requests 接口自動(dòng)化測(cè)試框架搭建教程

郵件已發(fā)送成功,我們進(jìn)入到郵箱中進(jìn)行查看,一切OK~~不過(guò)這我要說(shuō)明一下,我寫(xiě)的send_email是調(diào)用的outlook,如果您的電腦本地是使用的其他郵件服務(wù)器的話,這塊的代碼需要修改為您想使用的郵箱調(diào)用代碼

如果遇到發(fā)送的多個(gè)收件人,但是只有第一個(gè)收件人可以收到郵件,或者收件人為空可以參考http://www.361way.com/smtplib-multiple-addresses/5503.html

Python+unittest+requests 接口自動(dòng)化測(cè)試框架搭建教程

繼續(xù)往下走,這下我們?cè)搫?chuàng)建我們的runAll.py文件了

import osimport common.HTMLTestRunner as HTMLTestRunnerimport getpathInfoimport unittestimport readConfigfrom common.configEmail import SendEmailfrom apscheduler.schedulers.blocking import BlockingSchedulerimport pythoncom# import common.Logsend_mail = SendEmail( username=’@163.com’, passwd=’’, recv=[’’], title=’’, content=’測(cè)試發(fā)送郵件’, file=r’E:test_recordv2.3.3測(cè)試截圖調(diào)整樣式.png’, ssl=True, )path = getpathInfo.get_Path()report_path = os.path.join(path, ’result’)on_off = readConfig.ReadConfig().get_email(’on_off’)# log = common.Log.loggerclass AllTest:#定義一個(gè)類AllTest def __init__(self):#初始化一些參數(shù)和數(shù)據(jù) global resultPath resultPath = os.path.join(report_path, 'report.html')#result/report.html self.caseListFile = os.path.join(path, 'caselist.txt')#配置執(zhí)行哪些測(cè)試文件的配置文件路徑 self.caseFile = os.path.join(path, 'testCase')#真正的測(cè)試斷言文件路徑 self.caseList = [] def set_case_list(self): ''' 讀取caselist.txt文件中的用例名稱,并添加到caselist元素組 :return: ''' fb = open(self.caseListFile) for value in fb.readlines(): data = str(value) if data != ’’ and not data.startswith('#'):# 如果data非空且不以#開(kāi)頭self.caseList.append(data.replace('n', ''))#讀取每行數(shù)據(jù)會(huì)將換行轉(zhuǎn)換為n,去掉每行數(shù)據(jù)中的n fb.close() def set_case_suite(self): ''' :return: ''' self.set_case_list()#通過(guò)set_case_list()拿到caselist元素組 test_suite = unittest.TestSuite() suite_module = [] for case in self.caseList:#從caselist元素組中循環(huán)取出case case_name = case.split('/')[-1]#通過(guò)split函數(shù)來(lái)將aaa/bbb分割字符串,-1取后面,0取前面 print(case_name+'.py')#打印出取出來(lái)的名稱 #批量加載用例,第一個(gè)參數(shù)為用例存放路徑,第一個(gè)參數(shù)為路徑文件名 discover = unittest.defaultTestLoader.discover(self.caseFile, pattern=case_name + ’.py’, top_level_dir=None) suite_module.append(discover)#將discover存入suite_module元素組 print(’suite_module:’+str(suite_module)) if len(suite_module) > 0:#判斷suite_module元素組是否存在元素 for suite in suite_module:#如果存在,循環(huán)取出元素組內(nèi)容,命名為suitefor test_name in suite:#從discover中取出test_name,使用addTest添加到測(cè)試集 test_suite.addTest(test_name) else: print(’else:’) return None return test_suite#返回測(cè)試集 def run(self): ''' run test :return: ''' try: suit = self.set_case_suite()#調(diào)用set_case_suite獲取test_suite print(’try’) print(str(suit)) if suit is not None:#判斷test_suite是否為空print(’if-suit’)fp = open(resultPath, ’wb’)#打開(kāi)result/20181108/report.html測(cè)試報(bào)告文件,如果不存在就創(chuàng)建#調(diào)用HTMLTestRunnerrunner = HTMLTestRunner.HTMLTestRunner(stream=fp, title=’Test Report’, description=’Test Description’)runner.run(suit) else:print('Have no case to test.') except Exception as ex: print(str(ex)) #log.info(str(ex)) finally: print('*********TEST END*********') #log.info('*********TEST END*********') fp.close() #判斷郵件發(fā)送的開(kāi)關(guān) if on_off == ’on’: send_mail.send_email() else: print('郵件發(fā)送開(kāi)關(guān)配置關(guān)閉,請(qǐng)打開(kāi)開(kāi)關(guān)后可正常自動(dòng)發(fā)送測(cè)試報(bào)告')# pythoncom.CoInitialize()# scheduler = BlockingScheduler()# scheduler.add_job(AllTest().run, ’cron’, day_of_week=’1-5’, hour=14, minute=59)# scheduler.start()if __name__ == ’__main__’: AllTest().run()

執(zhí)行runAll.py,進(jìn)到郵箱中查看發(fā)送的測(cè)試結(jié)果報(bào)告,打開(kāi)查看

Python+unittest+requests 接口自動(dòng)化測(cè)試框架搭建教程

然后繼續(xù),我們框架到這里就算基本搭建好了,但是缺少日志的輸出,在一些關(guān)鍵的參數(shù)調(diào)用的地方我們來(lái)輸出一些日志。從而更方便的來(lái)維護(hù)和查找問(wèn)題。

按目錄結(jié)構(gòu)繼續(xù)在common下創(chuàng)建Log.py,內(nèi)容如下:

import osimport loggingfrom logging.handlers import TimedRotatingFileHandlerimport getpathInfopath = getpathInfo.get_Path()log_path = os.path.join(path, ’result’) # 存放log文件的路徑class Logger(object): def __init__(self, logger_name=’logs…’): self.logger = logging.getLogger(logger_name) logging.root.setLevel(logging.NOTSET) self.log_file_name = ’logs’ # 日志文件的名稱 self.backup_count = 5 # 最多存放日志的數(shù)量 # 日志輸出級(jí)別 self.console_output_level = ’WARNING’ self.file_output_level = ’DEBUG’ # 日志輸出格式 self.formatter = logging.Formatter(’%(asctime)s - %(name)s - %(levelname)s - %(message)s’) def get_logger(self): '''在logger中添加日志句柄并返回,如果logger已有句柄,則直接返回''' if not self.logger.handlers: # 避免重復(fù)日志 console_handler = logging.StreamHandler() console_handler.setFormatter(self.formatter) console_handler.setLevel(self.console_output_level) self.logger.addHandler(console_handler) # 每天重新創(chuàng)建一個(gè)日志文件,最多保留backup_count份 file_handler = TimedRotatingFileHandler(filename=os.path.join(log_path, self.log_file_name), when=’D’, interval=1, backupCount=self.backup_count, delay=True, encoding=’utf-8’) file_handler.setFormatter(self.formatter) file_handler.setLevel(self.file_output_level) self.logger.addHandler(file_handler) return self.loggerlogger = Logger().get_logger()

然后我們?cè)谛枰覀冚敵鋈罩镜牡胤教砑尤罩荆?/p>

我們修改runAll.py文件,在頂部增加import common.Log,然后增加標(biāo)紅框的代碼

Python+unittest+requests 接口自動(dòng)化測(cè)試框架搭建教程

讓我們?cè)賮?lái)運(yùn)行一下runAll.py文件,發(fā)現(xiàn)在result下多了一個(gè)logs文件,我們打開(kāi)看一下有沒(méi)有我們打印的日志

Python+unittest+requests 接口自動(dòng)化測(cè)試框架搭建教程

Python+unittest+requests 接口自動(dòng)化測(cè)試框架搭建教程

OK,至此我們的接口自動(dòng)化測(cè)試的框架就搭建完了,后續(xù)我們可以將此框架進(jìn)行進(jìn)一步優(yōu)化改造,使用我們真實(shí)項(xiàng)目的接口,結(jié)合持續(xù)集成定時(shí)任務(wù)等,讓這個(gè)項(xiàng)目每天定時(shí)的來(lái)跑啦~~~

歡迎添加我的微信,相互學(xué)習(xí)探討~1305618688,qq交流群:849102042

2020年9月23追加

一、、最近有太多人反饋,執(zhí)行通過(guò)后report.html文件中內(nèi)容為空,這個(gè)基本上多數(shù)原因是因?yàn)橛美龍?zhí)行異常報(bào)錯(cuò),導(dǎo)致沒(méi)有成功執(zhí)行用例,所以沒(méi)有生成數(shù)據(jù)。大家可以運(yùn)行testCase下的test01Case.py等用例文件,看是不是運(yùn)行報(bào)錯(cuò)了。如果運(yùn)行成功,再去執(zhí)行runAll試一下

完整的框架源碼下載

到此這篇關(guān)于Python+unittest+requests 接口自動(dòng)化測(cè)試框架搭建教程的文章就介紹到這了,更多相關(guān)Python+unittest+requests 接口自動(dòng)化測(cè)試內(nèi)容請(qǐng)搜索好吧啦網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持好吧啦網(wǎng)!

標(biāo)簽: Python 編程
相關(guān)文章:
主站蜘蛛池模板: 尤物精品 | 成人网免费观看 | 国产younv真实 | 狠狠五月天中文字幕 | 国产在视频线精品视频 | 黄色在线免费网站 | 精品国产一区二区三区不卡在线 | 国产成人精品免费 | 国产亚洲在线 | 美女污污网站 | 久草中文在线视频 | 日本一级爽毛片在线看 | 图片专区亚洲色图 | 拍拍在线观看免费播放 | 久久久99精品久久久 | 欧美艳星性videose精品 | 伊人日本 | 国产成人免费观看 | 亚洲91色 | 久久久亚洲欧洲日产国码二区 | 在线免费观看一级毛片 | 手机在线观看黄色 | 久久乐国产精品亚洲综合m3u8 | 网红思瑞一区二区三区 | 99视频在线观看视频一区 | 黄色影片在线看 | 亚洲欧美日韩中文v在线 | 国产精品自拍第一页 | 日韩一区二区三区免费体验 | 亚洲不卡影院 | 国产福利91精品一区二区 | 日韩 欧美 亚洲 中文字幕 | 不卡久久 | 国产手机精品自拍视频 | 高清毛片在线看高清 | 国产自产视频在线观看香蕉 | 波多野结衣黑人系列在线观看 | 18岁免费网站 | 国产一区二区在线观看麻豆 | 国产亚洲精品成人婷婷久久小说 | 国产精品高清在线观看 |