『壹』 python3實現自動化測試 [基於python語言實現自動化測試的研究]
[摘 要]自動化測試近年來的技術已經越來越成熟,在某些方面有著不可替代的作用,例如在性能測試,壓力測試中,自動化測試可以模擬成千上萬個用戶對目標程序進行測試。本文通過對大型實際項目的分析研究,分析針對某一產品的自動化測試框架。然後討論怎樣用python實現自動化測試。
[關鍵詞]測試技術手工測試自動化測試python腳本
[中圖分類號]TP3[文獻標識碼]A[文章編號]1007-9416(2010)03-0088-01
地添加測試用例,為測試用例提供公用函數,執行測試用例,發送測試結果等功能。
1 自動化測試的重要概念
檢查點(CheckPoint):將特定屬性的當前數據與期望數據進行比較的地方,用於判定被測試程序的功能是否正確。
成本收益比:並不是所有的測試都適合自動化測試,衡量一個用例是否適合自動化測試一個很重要的參考是國際上流行的自動化測試成本收益比,即是p=k*n/c1+c2。各個參數的意義下:
K=手工執行自動化測試案例所花費的時間成本。
N=自動化測試案例執行的次數
C1=花費在自動化測試前期的(時間成本+人力成本+金錢成本)
C2=花費在自動化測試後期的(時間成本+人力成本+金錢成本)
二八定律:1897年義大利經濟學家帕列托發現的二八定律在軟體行業同樣適用,而可以給我們很多啟發,指導我們的軟體開發和測試。80%的用戶經常使用的是20%的軟體功能。在軟體測試中,80%的bug是集中在20%的軟體模塊中,對於自動化測試來說,找出這20%的測試用例是至關重要的。
2 自動化測試的執行步驟
每次腳本都是從一個統一的文件開始執行的,就是如上的Start.py。這樣做的好處是可以把每個腳本都需要處理的工作放到一個文件中去執行,例如收集一些配置信息,讀取命令行參數。以這樣統一的處理風格為腳本的可讀性提供了保證,也為簡化了測試腳本的編寫,不用每次都要處理一些基本的事務。
啟動文件Start.py首先會讀取命行參數,如pthon Start.py -s FileMenu.suite -t FileNew
通過python的內置函數sys.argv就可以讀取命令行參數吵肢,非常方便。讀取到命令行參數後,在Start.py內部可以判斷命令行的格式是否符合我們的格式,如測試人員不小心把-s 寫成了-z 這樣就要退出測試執行。
如果輸入的格式是正確的,Start.py 負責在特定的目錄下尋找特定的Suite文件和Testcase。Suite文件和Testcase的格式會在下邊的具體實例中作介紹。
找到特定的Testcase後就可以執行測試用例,根據檢查點的通過或失敗發送測試報告,該報告會以網頁的形式顯示,方便測試人員和開發人員的查找調試。
3 用python實現GUI測試
圖形用戶界面(GUI) 就是使用圖象,輸入的文字,帶圖標的計算機界面,取而代之了許多鍵盤的功能。GUI可以讓用戶通過圖標和滑鼠與計算機進行交互,而不是單調地在命令行中輸入文本進行操作。設計良好的圖形用戶界面可以使用戶從命令中解放出來。
GUI測試主要包括兩個方面:一是純GUI測試,主要關注應用程序上GUI組件是否符合規范或是用戶的使用習慣,二是功能測試,主要是檢驗和驗證系統是否實現了系統的業務需求,旨在驗證系統的業務實現能力。但事實上兩者不是完全獨立的,一方面GUI的測試必定要觸發功能,另一方面,功能測試也一定要通過GUI將搜碰洞事件傳遞給後台服務。
3.1 編寫測試用例
ID 466540 :: Test CaseGeneral UI File Menu
Version 2
世枯PriorityP1
Summary:Verify File New window
Steps
SelectFile -> New
Expected Results
1. The VM creation window should open.
Keywords: i18n
Requirements : None
Created on 09/22/2008 20:58:23by wangw
Last modified on04/17/2009 00:20:53by marian
3.2 測試用例分析
以上是一個完整測試用例, 該測試用例包括:
測試ID 466540 , 有了測試ID就可以在測試人員提交bug後,QA或開發人員通過ID找到這個測試用例。還有一個更大的用處就是,在自動個腳本生成測試報告後,可以根據測試ID把該測試用例顯示在測試報告中,以供測試人員和開發人員調試。
測試名稱,根據測試名稱應該可以很快了解測試用例的內容,所以好的測試名稱也是非常重要的。
測試用例的版本(Version)。
測試優先順序(Priority),測試優先順序也是一個很重要的參數,因為大型項目都要有很多測試用例要執行。只有明確測試優先順序才能確保重要的測試用例得以及時進行,保證軟體質量。
測試用例概述(summary),幫助測試執行人員了解該測試用例的用測的功能。
測試步驟,描述測試人員或是自動化腳本每一步是怎樣操作的,例如本例告訴測試人員選擇菜單Fie,然後選擇菜單項New。
預期結果(Expected Results),說明經過以上測試步驟,期望程序運行出現的結果。
4 結語
本文在明確軟體測試理論的基礎上,對自動化測試做了重點闡述,通過實際項目的自動化測試分析,有些測試用例特別適合用自動化測試。例如GUI測試中,用些是要驗證界面元素是否顯示正常。如果是腳本就可以准確無誤地很快驗證完畢,而用人工驗證不僅容易出錯而且費時間。由於時間和硬體條件有限,本論文規避了許多問題,所以仍有許多工作需要完成。例如:做好腳本的復用,使測試腳本不斷積累。及研究怎樣在測試工具和自己搭建框架中尋找平衡等。
[參考文獻]
[1] 張克東.《軟體工程與軟體測試自動化教程》.北京:電子工業出版社,2002.
[2] 朱菊,王志堅,楊雪.《基於數據驅動的軟體自動化測試框架》[J]計算機技術測試與發展,2006.
[3] 馬瑞芳,王會燃.《計算機軟體測試方法的研究》.小型微型計算機系統,2003.
[4] 朱鴻,金凌紫.《軟體質量保障和測試》[M].北京:電子科學出版社,1997.
本文為全文原貌 未安裝PDF瀏覽器用戶請先下載安裝 原版全文
『貳』 怎麼用python做自動化測試
這里以web自動化測試為例,簡單介紹一下如何使用python進行web自動化測試,主要用到selenium這個框架,實驗環境win10+python3.6,主要內容如下:
1.首先,安裝selenium框架,這個直接在cmd窗口輸入命令「pipinstallselenium」就行,如下,安裝非常快:
2.安裝完成後,還需要安裝瀏覽器驅動程序,不然直接運行程序會報錯,以谷歌瀏覽器chrome為例,需要下載chromedriver驅動程序,如下,這里chromedriver的版本必須要與自己平台瀏覽器的版本匹配:
下載完成後,是一個zip壓縮包,裡面就一個chromedriver.exe文件,這里需要將這個文件復制到python安裝目錄下,如下:
3.最後,我們就可以進行selenium框架測試了,測試代碼如下,非常簡單,創建一個webdriver,如果能正常打開對應網頁,則說明selenium安裝成功:
之後就可以直接定位相關元攜余素,進行web自動化測試了,主要方法如下(共有8種),辯洞滾分別是id、name、classname、tagname、linktext、partiallinktext、xpath和cssselector,這里可以自行測試,相關資料非常豐富:
至此,我們就完成了pythonweb自動化測試框架selenium的安裝和簡單使用。總的來說,整個過程非常簡單,只要你有一定的python基礎,熟悉一下上面的安裝過程,很快就能搭建好本地selenium自動化測試框架,網上也顫芹有相關教程和資料,介紹的非常詳細,感興趣的話,可以搜一下,希望以上分享的內容能對你有所幫助吧,也歡迎大家評論、留言進行補充。
『叄』 Python自動化測試工具大概有哪些_軟體自動化測試工具有哪些
一、對於你的問題,首先明確測試類型,然後才能明確自動化測試類型,最後定位哪個類型用哪個方面的自動化測試工具。
1、測試類型可以包括:白盒測試、黑盒測試(功能測試、性能測試)等。
2、不同的測試類型使用的自動化測試方法不同,白盒測試主要針對代碼級的單元測試、黑盒測試主要面對功能級和系統級的驗證測試。
3、自動化測試拍尺,針對白盒測試,一般需要有一定的編程基礎,即能夠基於功能代碼寫測試代碼,常用的單元測試方面的自動化測試工具很多,上網一搜全是。
4、自動化測試,針對功能測試,有幾種情況,基於CLI、API和GUI的測試;基於CLI、API的測試,即應用腳本技術向設備模擬發送CLI命令或者API請求,以達到控制設備的效果。基於GUI功能測試,即應用傳統的界面自動化測試工具(例如:RFT、QTP等)控制界面控制項操作的方法,以達到模擬用戶操作,這幾種方式都需要你有一定的編碼基礎;基於CLI、API的需要你懂腳本技術(例如:tcl、python、ruby等),RFT需要你懂java或者.net、QTP需要VB等。
5、你說的loadrunner就是性能測試方面的工具,即是測試軟體性能、例如多用戶操作等性能、也需要寫代碼,LR腳本支持的語言有:java、
c、VisualBasic、vbscrīpt。默認的腳本生成語言為
C;其實我想說的是,性能測試工具不重要,你需要掌握其性能測試的方法才是更重要的。
二、我感覺你想入門自動化測試,但是從你問的問題來看,有一定盲目性,我簡單說一下自動化測試吧。
1、自動化測試,其理念就是應用各種手段模擬人工操作,節省人力測試成本,保證產品測試質量。
2、你想學好自動化軟體測試,不是單單靠學習幾個自動化工具就能掌握的,但是你可以從工具入手,首先,告訴你自動化測試的基礎是:
1)編程技巧,包括高級語言和腳本語言,腳本語言是初期的掌握,可以有,tcl、phython、ruby等而高級語言,要好好學好一門,例如,我是對java為重點。還有,如果你是對web自動化測試的話,那麼jsp、php、HTML、CSS等web語言是必須掌握的。
2)操作系統技巧;因為軟體自動化測試是構建在操作系統上的,其技巧需要能善於利用到操作系統的各種技巧,例如:注冊表、環境變數、句柄等。
3)資料庫知識,要善於利用資料庫知識去存儲管理。
4)業務知識,這也是重點。你所在軟體行業的軟體業務,要知道你的軟體的工作方式。
5)質量與流程管理理念。
然後,你的學習步驟:
1)可以從工具入手,根據具體的項目去學習;例如:java軟體界面測試(RFT、QTP的java插襲孝高件等)、web界面測試(QTP、
selenium等)、性能測試(RPT、loadrunner等)。但記住,學習其工具,重點不是簡單的使用,而是如何利用工具去擴展。
2)然後,重點學習以上的基礎,以編程為重點,其餘的結合學習,順便說一句,其實自動化測試的理念與軟體設計模式理念很像,你可以從中有所領悟。
3)之後,再學習去拓建自己的自動化測試框架,何謂框架,一下說不清楚,我給你推薦一慎灶下。
注意:如果沒有自動化測試方面的實踐項目的話,最好先從基礎學起,因為基礎學好了,自動化測試入門會很快的。
4(至於性能測試,也是一樣,可以先從工具入手,但不要局限於工具,性能測試最重要的是環境的構建方法以及對測試結果的分析方法,所以性能測試重點在於分析和實現過程,而不是工具使用過程。
『肆』 如何使用python 語言來實現測試開發
對於各種驅動介面,Python來編寫測試用例的好處是:由於Python不需要編譯,你所執行的也就是你所編寫的,當發生異常的時候,你無須打開集成開發環境,載入測試工程、並調試,你能夠很方便的看到python測試腳本的內容,什麼地方出了異常可以立刻發現,例如:
from ctypes import *
rc =c_int(-12345);
dll = windll.LoadLibrary("dmodbc.dll");#載入被測試組件
#=================#
SQLHANDLE_env = pointer(c_long(0));
SQLHANDLE_cnn = pointer(c_long(0));
SQLHANDLE_stmt = pointer(c_long(0));
pdns = c_char_p("FASTDB");
puid = c_char_p("SYSDBA");
ppwd = c_char_p("SYSDBA");
#env handle
rc = dll.SQLAllocHandle(1,None,byref(SQLHANDLE_env));
print "result of henv handle alloc :%d" %rc;
#cnn handle
rc = dll.SQLAllocHandle(2,SQLHANDLE_env,byref(SQLHANDLE_cnn));
print "result of cnn handle alloc :%d" %rc;
#connect!
rc = dll.SQLConnect(SQLHANDLE_cnn,pdns,-3,puid,-3,ppwd,-3)
print "result of connect :%d" %rc;
#stmt handle
rc = dll.SQLAllocHandle(3,SQLHANDLE_cnn,byref(SQLHANDLE_stmt));
print "result of stmt handle alloc:%d" %rc;
#exec
rc = dll.SQLExecDirect(SQLHANDLE_stmt,"insert into t values(1)",-3);
print "result of exec:%d" %rc;
#free========================
rc = dll.SQLFreeHandle(3, SQLHANDLE_stmt);
print rc;
rc = dll.SQLDisconnect(SQLHANDLE_cnn);
print rc;
rc = dll.SQLFreeHandle(2, SQLHANDLE_cnn);
print rc;
rc = dll.SQLFreeHandle(1, SQLHANDLE_env);
print rc;
在上面我們可以看到,Python調用c/c++介面是十分容易的,只需要把動態庫載入進來,然後把這個動態庫當作一個對象實例來使用就可以了。下面將是一個使用ado.net介面的例子:
import System;
from Dm import *#Dm是DMDBMS提供的ado.Net的DataProvider
#print dir(Dm.DmCommand);
i =0;
cnn = Dm.DmConnection("server = 127.0.0.1; User ID = SYSDBA; PWD = SYSDBA; Database = SYSTEM; port = 12345");
cmd = Dm.DmCommand();
cmd.Connection = cnn;
cmd.CommandText = "insert into t values(1);";
cnn.Open();
i=cmd.ExecuteNonQuery();
print i;
cmd.Dispose();
cnn.Close();
可以看到,.net對象的使用與在VisualStdio上進行開發幾乎沒有任何區別。
通過使用Python進行測試用例的開發,最大的好處莫過於:學習成本非常低,測試工程師只需要學習Python,對於其他語言稍有了解就可以了。同時只需要少量的測試開發工程師對Python測試框架進行維護。
這樣的好處就是便於測試人員將精力專精在一個方向,免於「什麼都會一點,但什麼都不精」的情況。當然測試人員具備廣闊的知識面,會使用各種常見的開發工具與平台是好事情,並且也是必要的,不過在短時間內要求迅速能夠勝任大多數任務也是企業在人才培養上的期望目標。
『伍』 python的性能
PPT的性能,這個你也找找這方面的消息吧,關於這個性能的一些介紹上多了解一下這個情況。
『陸』 Python自動化測試框架有哪些
1、Unittest
是python內置的標准類庫,它的API跟java的Junit、.net的NUnit、C++的CppUnit很相似,通過繼承unittest.TestCase來創建一個測試用例。
2、 Doctest
Doctest模塊會搜索那些看起來像互動式會話的python代碼片段,然後嘗試執行並驗證結果,即使從來沒有接觸過Doctest,我們也可以從這個名字中窺到一絲端倪,它看起來就好像代碼里的文檔字元串(docstring)一樣。
3、py.test
是python的一種單元測試框架,與python自帶的unittest測試框架類似,但是比unittest框架使用起來更加簡潔、效率更高;根據官方介紹,它具有以下特點:非常容易上手、入門簡單、文檔豐富,有很多實例可以參考;能夠支持簡單的單元測試和復雜的功能測試;支持參數化;支持重復執行失敗的case。
4、Nose
Nose是對unittest的擴展,使得python的測試更加簡單,Nose自動發現測試代碼並執行,提供了大量的插件,nose不是python自帶模塊,需要用pip安裝。
5、tox
最大的特色,是自動化測試環境的管理以及使用多個解析器配置進行測試;
6、Unittest2
是Unittest的升級版本,對API進行了改善以及更好的診斷語法。
『柒』 python寫的測試框架怎麼使用
安裝
>pipinstall-Upytest#通過pip安裝
>py.test--version#查看pytest版本
Thisispytestversion2.7.2,importedfromC:Python27libsite-packagespytest.pyc
簡單的測試
讓我們創建第一個文件,對個簡單的功能進行測試。
好吧!其實, 我也不理解這段代碼的含義,但是執行它的可運行測試用例了。
pytest/
├──test_case/
│├── test_sample.py
│├──test_class.py
│├── __init__.py
│ └──test_case2/
│ ├── test_main.py
│ ├── test_time.py
│ └──__init__.py
└──runtests.py
執行runtest.py文件。
>python runtest.py
當然,你也可以打開runtests.py 文件運行它。
===================================================================
*最後,pytest是如果識別測試用例的呢?它默認使用檢查以test_ *.py 或*_test.py命名的文件名,在文件內部查找以test_打頭的方法或函數,並執行它們。
pytest還有許多需要討論的地方,做為這個系列的第一節,先介紹到這里。
『捌』 後端編程Python3-調試、測試和性能剖析(下)
單元測試(Unit Testing)
為程序編寫測試——如果做的到位——有助於減少bug的出現,並可以提高我們對程序按預期目標運行的信心。通常,測試並不能保證正確性,因為對大多數程序而言, 可能的輸入范圍以及可能的計算范圍是如此之大,只有其中最小的一部分能被實際地進 行測試。盡管如此,通過仔細地選擇測試的方法和目標,可以提高代碼的質量。
大量不同類型的測試都可以進行,比如可用性測試、功能測試以及整合測試等。這里, 我們只講單元測試一對單獨的函數、類與方法進行測試,確保其符合預期的行為。
TDD的一個關鍵點是,當我們想添加一個功能時——比如為類添加一個方法—— 我們首次為其編寫一個測試用例。當然,測試將失敗,因為我們還沒有實際編寫該方法。現在,我們編寫該方法,一旦方法通過了測試,就可以返回所有測試,確保我們新添加的代碼沒有任何預期外的副作用。一旦所有測試運行完畢(包括我們為新功能編寫的測試),就可以對我們的代碼進行檢查,並有理有據地相信程序行為符合我們的期望——當然,前提是我們的測試是適當的。
比如,我們編寫了一個函數,該函數在特定的索引位置插入一個字元串,可以像下面這樣開始我們的TDD:
def insert_at(string, position, insert):
"""Returns a of string with insert inserted at the position
>>> string = "ABCDE"
>>> result =[]
>>> for i in range(-2, len(string) + 2):
... result.append(insert_at(string, i,「-」))
>>> result[:5]
['ABC-DE', 'ABCD-E', '-ABCDE','A-BCDE', 'AB-CDE']
>>> result[5:]
['ABC-DE', 'ABCD-E', 'ABCDE-', 'ABCDE-']
"""
return string
對不返回任何參數的函數或方法(通常返回None),我們通常賦予其由pass構成的一個suite,對那些返回值被試用的,我們或者返回一個常數(比如0),或者某個不變的參數——這也是我們這里所做的。(在更復雜的情況下,返回fake對象可能更有用一一對這樣的類,提供mock對象的第三方模塊是可用的。)
運行doctest時會失敗,並列出每個預期內的字元串('ABCD-EF'、'ABCDE-F' 等),及其實際獲取的字元串(所有的都是'ABCD-EF')。一旦確定doctest是充分的和正確的,就可以編寫該函數的主體部分,在本例中只是簡單的return string[:position] + insert+string[position:]。(如果我們編寫的是 return string[:position] + insert,之後復制 string [:position]並將其粘貼在末尾以便減少一些輸入操作,那麼doctest會立即提示錯誤。)
Python的標准庫提供了兩個單元測試模塊,一個是doctest,這里和前面都簡單地提到過,另一個是unittest。此外,還有一些可用於Python的第三方測試工具。其中最著名的兩個是nose (code.google.com/p/python-nose)與py.test (codespeak.net/py/dist/test/test.html), nose 致力於提供比標準的unittest 模塊更廣泛的功能,同時保持與該模塊的兼容性,py.test則採用了與unittest有些不同的方法,試圖盡可能消除樣板測試代碼。這兩個第三方模塊都支持測試發現,因此沒必要寫一個總體的測試程序——因為模塊將自己搜索測試程序。這使得測試整個代碼樹或某一部分 (比如那些已經起作用的模塊)變得很容易。那些對測試嚴重關切的人,在決定使用哪個測試工具之前,對這兩個(以及任何其他有吸引力的)第三方模塊進行研究都是值 得的。
創建doctest是直截了當的:我們在模塊中編寫測試、函數、類與方法的docstrings。 對於模塊,我們簡單地在末尾添加了 3行:
if __name__ =="__main__":
import doctest
doctest.testmod()
在程序內部使用doctest也是可能的。比如,blocks.py程序(其模塊在後面)有自己函數的doctest,但以如下代碼結尾:
if __name__== "__main__":
main()
這里簡單地調用了程序的main()函數,並且沒有執行程序的doctest。要實驗程序的 doctest,有兩種方法。一種是導入doctest模塊,之後運行程序---比如,在控制台中輸 入 python3 -m doctest blocks.py (在 Wndows 平台上,使用類似於 C:Python3 lpython.exe 這樣的形式替代python3)。如果所有測試運行良好,就沒有輸出,因此,我們可能寧願執行python3-m doctest blocks.py-v,因為這會列出每個執行的doctest,並在最後給出結果摘要。
另一種執行doctest的方法是使用unittest模塊創建單獨的測試程序。在概念上, unittest模塊是根據Java的JUnit單元測試庫進行建模的,並用於創建包含測試用例的測試套件。unittest模塊可以基於doctests創建測試用例,而不需要知道程序或模塊包含的任何事物——只要知道其包含doctest即可。因此,為給blocks.py程序製作一個測試套件,我們可以創建如下的簡單程序(將其稱為test_blocks.py):
import doctest
import unittest
import blocks
suite = unittest.TestSuite()
suite.addTest(doctest.DocTestSuite(blocks))
runner = unittest.TextTestRunner()
print(runner.run(suite))
注意,如果釆用這種方法,程序的名稱上會有一個隱含的約束:程序名必須是有效的模塊名。因此,名為convert-incidents.py的程序的測試不能寫成這樣。因為import convert-incidents不是有效的,在Python標識符中,連接符是無效的(避開這一約束是可能的,但最簡單的解決方案是使用總是有效模塊名的程序文件名,比如,使用下劃線替換連接符)。這里展示的結構(創建一個測試套件,添加一個或多個測試用例或測試套件,運行總體的測試套件,輸出結果)是典型的機遇unittest的測試。運行時,這一特定實例產生如下結果:
...
.............................................................................................................
Ran 3 tests in 0.244s
OK
每次執行一個測試用例時,都會輸出一個句點(因此上面的輸出最前面有3個句點),之後是一行連接符,再之後是測試摘要(如果有任何一個測試失敗,就會有更多的輸出信息)。
如果我們嘗試將測試分離開(典型情況下是要測試的每個程序和模塊都有一個測試用例),就不要再使用doctests,而是直接使用unittest模塊的功能——尤其是我們習慣於使用JUnit方法進行測試時ounittest模塊會將測試分離於代碼——對大型項目(測試編寫人員與開發人員可能不一致)而言,這種方法特別有用。此外,unittest單元測試編寫為獨立的Python模塊,因此,不會像在docstring內部編寫測試用例時受到兼容性和明智性的限制。
unittest模塊定義了 4個關鍵概念。測試夾具是一個用於描述創建測試(以及用完之後將其清理)所必需的代碼的術語,典型實例是創建測試所用的一個輸入文件,最後刪除輸入文件與結果輸出文件。測試套件是一組測試用例的組合。測試用例是測試的基本單元—我們很快就會看到實例。測試運行者是執行一個或多個測試套件的對象。
典型情況下,測試套件是通過創建unittest.TestCase的子類實現的,其中每個名稱 以「test」開頭的方法都是一個測試用例。如果我們需要完成任何創建操作,就可以在一個名為setUp()的方法中實現;類似地,對任何清理操作,也可以實現一個名為 tearDown()的方法。在測試內部,有大量可供我們使用的unittest.TestCase方法,包括 assertTrue()、assertEqual()、assertAlmostEqual()(對於測試浮點數很有用)、assertRaises() 以及更多,還包括很多對應的逆方法,比如assertFalse()、assertNotEqual()、failIfEqual()、 failUnlessEqual ()等。
unittest模塊進行了很好的歸檔,並且提供了大量功能,但在這里我們只是通過一 個非常簡單的測試套件來感受一下該模塊的使用。這里將要使用的實例,該練習要求創建一個Atomic模塊,該模塊可以用作一 個上下文管理器,以確保或者所有改變都應用於某個列表、集合或字典,或者所有改變都不應用。作為解決方案提供的Atomic.py模塊使用30行代碼來實現Atomic類, 並提供了 100行左右的模塊doctest。這里,我們將創建test_Atomic.py模塊,並使用 unittest測試替換doctest,以便可以刪除doctest。
在編寫測試模塊之前,我們需要思考都需要哪些測試。我們需要測試3種不同的數據類型:列表、集合與字典。對於列表,需要測試的是插入項、刪除項或修改項的值。對於集合,我們必須測試向其中添加或刪除一個項。對於字典,我們必須測試的是插入一個項、修改一個項的值、刪除一個項。此外,還必須要測試的是在失敗的情況下,不會有任何改變實際生效。
結構上看,測試不同數據類型實質上是一樣的,因此,我們將只為測試列表編寫測試用例,而將其他的留作練習。test_Atomic.py模塊必須導入unittest模塊與要進行測試的Atomic模塊。
創建unittest文件時,我們通常創建的是模塊而非程序。在每個模塊內部,我們定義一個或多個unittest.TestCase子類。比如,test_Atomic.py模塊中僅一個單獨的 unittest-TestCase子類,也就是TestAtomic (稍後將對其進行講解),並以如下兩行結束:
if name == "__main__":
unittest.main()
這兩行使得該模塊可以單獨運行。當然,該模塊也可以被導入並從其他測試程序中運行——如果這只是多個測試套件中的一個,這一點是有意義的。
如果想要從其他測試程序中運行test_Atomic.py模塊,那麼可以編寫一個與此類似的程序。我們習慣於使用unittest模塊執行doctests,比如:
import unittest
import test_Atomic
suite = unittest.TestLoader().loadTestsFromTestCase(test_Atomic.TestAtomic)
runner = unittest.TextTestRunner()
pnnt(runner.run(suite))
這里,我們已經創建了一個單獨的套件,這是通過讓unittest模塊讀取test_Atomic 模塊實現的,並且使用其每一個test*()方法(本實例中是test_list_success()、test_list_fail(),稍後很快就會看到)作為測試用例。
我們現在將查看TestAtomic類的實現。對通常的子類(不包括unittest.TestCase 子類),不怎麼常見的是,沒有必要實現初始化程序。在這一案例中,我們將需要建立 一個方法,但不需要清理方法,並且我們將實現兩個測試用例。
def setUp(self):
self.original_list = list(range(10))
我們已經使用了 unittest.TestCase.setUp()方法來創建單獨的測試數據片段。
def test_list_succeed(self):
items = self.original_list[:]
with Atomic.Atomic(items) as atomic:
atomic.append(1999)
atomic.insert(2, -915)
del atomic[5]
atomic[4]= -782
atomic.insert(0, -9)
self.assertEqual(items,
[-9, 0, 1, -915, 2, -782, 5, 6, 7, 8, 9, 1999])
def test_list_fail(self):
items = self.original_list[:]
with self.assertRaises(AttributeError):
with Atomic.Atomic(items) as atomic:
atomic.append(1999)
atomic.insert(2, -915)
del atomic[5]
atomic[4] = -782
atomic.poop() # Typo
self.assertListEqual(items, self.original_list)
這里,我們直接在測試方法中編寫了測試代碼,而不需要一個內部函數,也不再使用unittest.TestCase.assertRaised()作為上下文管理器(期望代碼產生AttributeError)。 最後我們也使用了 Python 3.1 的 unittest.TestCase.assertListEqual()方法。
正如我們已經看到的,Python的測試模塊易於使用,並且極為有用,在我們使用 TDD的情況下更是如此。它們還有比這里展示的要多得多的大量功能與特徵——比如,跳過測試的能力,這有助於理解平台差別——並且這些都有很好的文檔支持。缺失的一個功能——但nose與py.test提供了——是測試發現,盡管這一特徵被期望在後續的Python版本(或許與Python 3.2—起)中出現。
性能剖析(Profiling)
如果程序運行很慢,或者消耗了比預期內要多得多的內存,那麼問題通常是選擇的演算法或數據結構不合適,或者是以低效的方式進行實現。不管問題的原因是什麼, 最好的方法都是准確地找到問題發生的地方,而不只是檢査代碼並試圖對其進行優化。 隨機優化會導致引入bug,或者對程序中本來對程序整體性能並沒有實際影響的部分進行提速,而這並非解釋器耗費大部分時間的地方。
在深入討論profiling之前,注意一些易於學習和使用的Python程序設計習慣是有意義的,並且對提高程序性能不無裨益。這些技術都不是特定於某個Python版本的, 而是合理的Python程序設計風格。第一,在需要只讀序列時,最好使用元組而非列表; 第二,使用生成器,而不是創建大的元組和列表並在其上進行迭代處理;第三,盡量使用Python內置的數據結構 dicts、lists、tuples 而不實現自己的自定義結構,因為內置的數據結構都是經過了高度優化的;第四,從小字元串中產生大字元串時, 不要對小字元串進行連接,而是在列表中累積,最後將字元串列表結合成為一個單獨的字元串;第五,也是最後一點,如果某個對象(包括函數或方法)需要多次使用屬性進行訪問(比如訪問模塊中的某個函數),或從某個數據結構中進行訪問,那麼較好的做法是創建並使用一個局部變數來訪問該對象,以便提供更快的訪問速度。
Python標准庫提供了兩個特別有用的模塊,可以輔助調査代碼的性能問題。一個是timeit模塊——該模塊可用於對一小段Python代碼進行計時,並可用於諸如對兩個或多個特定函數或方法的性能進行比較等場合。另一個是cProfile模塊,可用於profile 程序的性能——該模塊對調用計數與次數進行了詳細分解,以便發現性能瓶頸所在。
為了解timeit模塊,我們將查看一些小實例。假定有3個函數function_a()、 function_b()、function_c(), 3個函數執行同樣的計算,但分別使用不同的演算法。如果將這些函數放於同一個模塊中(或分別導入),就可以使用timeit模塊對其進行運行和比較。下面給出的是模塊最後使用的代碼:
if __name__ == "__main__":
repeats = 1000
for function in ("function_a", "function_b", "function_c"):
t = timeit.Timer("{0}(X, Y)".format(function),"from __main__ import {0}, X, Y".format(function))
sec = t.timeit(repeats) / repeats
print("{function}() {sec:.6f} sec".format(**locals()))
賦予timeit.Timer()構造子的第一個參數是我們想要執行並計時的代碼,其形式是字元串。這里,該字元串是「function_a(X,Y)」;第二個參數是可選的,還是一個待執行的字元串,這一次是在待計時的代碼之前,以便提供一些建立工作。這里,我們從 __main__ (即this)模塊導入了待測試的函數,還有兩個作為輸入數據傳入的變數(X 與Y),這兩個變數在該模塊中是作為全局變數提供的。我們也可以很輕易地像從其他模塊中導入數據一樣來進行導入操作。
調用timeit.Timer對象的timeit()方法時,首先將執行構造子的第二個參數(如果有), 之後執行構造子的第一個參數並對其執行時間進行計時。timeit.Timer.timeit()方法的返回值是以秒計數的時間,類型是float。默認情況下,timeit()方法重復100萬次,並返回所 有這些執行的總秒數,但在這一特定案例中,只需要1000次反復就可以給出有用的結果, 因此對重復計數次數進行了顯式指定。在對每個函數進行計時後,使用重復次數對總數進行除法操作,就得到了平均執行時間,並在控制台中列印出函數名與執行時間。
function_a() 0.001618 sec
function_b() 0.012786 sec
function_c() 0.003248 sec
在這一實例中,function_a()顯然是最快的——至少對於這里使用的輸入數據而言。 在有些情況下一一比如輸入數據不同會對性能產生巨大影響——可能需要使用多組輸入數據對每個函數進行測試,以便覆蓋有代表性的測試用例,並對總執行時間或平均執行時間進行比較。
有時監控自己的代碼進行計時並不是很方便,因此timeit模塊提供了一種在命令行中對代碼執行時間進行計時的途徑。比如,要對MyMole.py模塊中的函數function_a()進行計時,可以在控制台中輸入如下命令:python3 -m timeit -n 1000 -s "from MyMole import function_a, X, Y" "function_a(X, Y)"(與通常所做的一樣,對 Windows 環境,我們必須使用類似於C:Python3lpython.exe這樣的內容來替換python3)。-m選項用於Python 解釋器,使其可以載入指定的模塊(這里是timeit),其他選項則由timeit模塊進行處理。 -n選項指定了循環計數次數,-s選項指定了要建立,最後一個參數是要執行和計時的代碼。命令完成後,會向控制台中列印運行結果,比如:
1000 loops, best of 3: 1.41 msec per loop
之後我們可以輕易地對其他兩個函數進行計時,以便對其進行整體的比較。
cProfile模塊(或者profile模塊,這里統稱為cProfile模塊)也可以用於比較函數 與方法的性能。與只是提供原始計時的timeit模塊不同的是,cProfile模塊精確地展示 了有什麼被調用以及每個調用耗費了多少時間。下面是用於比較與前面一樣的3個函數的代碼:
if __name__ == "__main__":
for function in ("function_a", "function_b", "function_c"):
cProfile.run("for i in ranged 1000): {0}(X, Y)".format(function))
我們必須將重復的次數放置在要傳遞給cProfile.run()函數的代碼內部,但不需要做任何創建,因為模塊函數會使用內省來尋找需要使用的函數與變數。這里沒有使用顯式的print()語句,因為默認情況下,cProfile.run()函數會在控制台中列印其輸出。下面給出的是所有函數的相關結果(有些無關行被省略,格式也進行了稍許調整,以便與頁面適應):
1003 function calls in 1.661 CPU seconds
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.003 0.003 1.661 1.661 :1 ( )
1000 1.658 0.002 1.658 0.002 MyMole.py:21 (function_a)
1 0.000 0.000 1.661 1.661 {built-in method exec}
5132003 function calls in 22.700 CPU seconds
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.487 0.487 22.700 22.700 : 1 ( )
1000 0.011 0.000 22.213 0.022 MyMole.py:28(function_b)
5128000 7.048 0.000 7.048 0.000 MyMole.py:29( )
1000 0.00 50.000 0.005 0.000 {built-in method bisectjeft}
1 0.000 0.000 22.700 22.700 {built-in method exec}
1000 0.001 0.000 0.001 0.000 {built-in method len}
1000 15.149 0.015 22.196 0.022 {built-in method sorted}
5129003 function calls in 12.987 CPU seconds
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.205 0.205 12.987 12.987 :l ( )
1000 6.472 0.006 12.782 0.013 MyMole.py:36(function_c)
5128000 6.311 0.000 6.311 0.000 MyMole.py:37( )
1 0.000 0.000 12.987 12.987 {built-in method exec}
ncalls ("調用的次數")列列出了對指定函數(在filename:lineno(function)中列出) 的調用次數。回想一下我們重復了 1000次調用,因此必須將這個次數記住。tottime (「總的時間」)列列出了某個函數中耗費的總時間,但是排除了函數調用的其他函數內部花費的時間。第一個percall列列出了對函數的每次調用的平均時間(tottime // ncalls)。 cumtime ("累積時間")列出了在函數中耗費的時間,並且包含了函數調用的其他函數內部花費的時間。第二個percall列列出了對函數的每次調用的平均時間,包括其調用的函數耗費的時間。
這種輸出信息要比timeit模塊的原始計時信息富有啟發意義的多。我們立即可以發現,function_b()與function_c()使用了被調用5000次以上的生成器,使得它們的速度至少要比function_a()慢10倍以上。並且,function_b()調用了更多通常意義上的函數,包括調用內置的sorted()函數,這使得其幾乎比function_c()還要慢兩倍。當然,timeit() 模塊提供了足夠的信息來查看計時上存在的這些差別,但cProfile模塊允許我們了解為什麼會存在這些差別。正如timeit模塊允許對代碼進行計時而又不需要對其監控一樣,cProfile模塊也可以做到這一點。然而,從命令行使用cProfile模塊時,我們不能精確地指定要執行的 是什麼——而只是執行給定的程序或模塊,並報告所有這些的計時結果。需要使用的 命令行是python3 -m cProfile programOrMole.py,產生的輸出信息與前面看到的一 樣,下面給出的是輸出信息樣例,格式上進行了一些調整,並忽略了大多數行:
10272458 function calls (10272457 primitive calls) in 37.718 CPU secs
ncalls tottime percall cumtime percall filename:lineno(function)
10.000 0.000 37.718 37.718 :1 ( )
10.719 0.719 37.717 37.717 :12( )
1000 1.569 0.002 1.569 0.002 :20(function_a)
1000 0.011 0.000 22.560 0.023 :27(function_b)
5128000 7.078 0.000 7.078 0.000 :28( )
1000 6.510 0.007 12.825 0.013 :35(function_c)
5128000 6.316 0.000 6.316 0.000 :36( )
在cProfile術語學中,原始調用指的就是非遞歸的函數調用。
以這種方式使用cProfile模塊對於識別值得進一步研究的區域是有用的。比如,這里 我們可以清晰地看到function_b()需要耗費更長的時間,但是我們怎樣獲取進一步的詳細資料?我們可以使用cProfile.run("function_b()")來替換對function_b()的調用。或者可以保存完全的profile數據並使用pstats模塊對其進行分析。要保存profile,就必須對命令行進行稍許修改:python3 -m cProfile -o profileDataFile programOrMole.py。 之後可以對 profile 數據進行分析,比如啟動IDLE,導入pstats模塊,賦予其已保存的profileDataFile,或者也可以在控制台中互動式地使用pstats。
下面給出的是一個非常短的控制台會話實例,為使其適合頁面展示,進行了適當調整,我們自己的輸入則以粗體展示:
$ python3 -m cProfile -o profile.dat MyMole.py
$ python3 -m pstats
Welcome to the profile statistics browser.
% read profile.dat
profile.dat% callers function_b
Random listing order was used
List reced from 44 to 1 e to restriction
Function was called by...
ncalls tottime cumtime
:27(function_b) <- 1000 0.011 22.251 :12( )
profile.dat% callees function_b
Random listing order was used
List reced from 44 to 1 e to restriction
Function called...
ncalls tottime cumtime
:27(function_b)->
1000 0.005 0.005 built-in method bisectJeft
1000 0.001 0.001 built-in method len
1000 1 5.297 22.234 built-in method sorted
profile.dat% quit
輸入help可以獲取命令列表,help後面跟隨命令名可以獲取該命令的更多信息。比如, help stats將列出可以賦予stats命令的參數。還有其他一些可用的工具,可以提供profile數據的圖形化展示形式,比如 RunSnakeRun (www.vrplumber.com/prograinming/runsnakerun), 該工具需要依賴於wxPython GUI庫。
使用timeit與cProfile模塊,我們可以識別出我們自己代碼中哪些區域會耗費超過預期的時間;使用cProfile模塊,還可以准確算岀時間消耗在哪裡。
以上內容部分摘自視頻課程 05後端編程Python-19調試、測試和性能調優(下) ,更多實操示例請參照視頻講解。跟著張員外講編程,學習更輕松,不花錢還能學習真本領。