def change(str1):
創(chuàng)新互聯(lián)是一家從事企業(yè)網(wǎng)站建設(shè)、成都網(wǎng)站建設(shè)、做網(wǎng)站、行業(yè)門戶網(wǎng)站建設(shè)、網(wǎng)頁設(shè)計制作的專業(yè)網(wǎng)站建設(shè)公司,擁有經(jīng)驗豐富的網(wǎng)站建設(shè)工程師和網(wǎng)頁設(shè)計人員,具備各種規(guī)模與類型網(wǎng)站建設(shè)的實力,在網(wǎng)站建設(shè)領(lǐng)域樹立了自己獨特的設(shè)計風格。自公司成立以來曾獨立設(shè)計制作的站點近1000家。
new_str = str()
for i in range(len(str1)):
if(65 = ord(str1[i]) = 90):
a = str1[i].lower()
print(a,end='')
elif(97 = ord(str1[i]) = 122):
a = str1[i].upper()
print(a,end='')
else:
a = str1[i]
print(a,end='')
return new_str
str2 = str(input("要轉(zhuǎn)換的字符串:"))
print(change(str2))
單元測試(Unit Testing)
為程序編寫測試——如果做的到位——有助于減少bug的出現(xiàn),并可以提高我們對程序按預(yù)期目標運行的信心。通常,測試并不能保證正確性,因為對大多數(shù)程序而言, 可能的輸入范圍以及可能的計算范圍是如此之大,只有其中最小的一部分能被實際地進 行測試。盡管如此,通過仔細地選擇測試的方法和目標,可以提高代碼的質(zhì)量。
大量不同類型的測試都可以進行,比如可用性測試、功能測試以及整合測試等。這里, 我們只講單元測試一對單獨的函數(shù)、類與方法進行測試,確保其符合預(yù)期的行為。
TDD的一個關(guān)鍵點是,當我們想添加一個功能時——比如為類添加一個方法—— 我們首次為其編寫一個測試用例。當然,測試將失敗,因為我們還沒有實際編寫該方法?,F(xiàn)在,我們編寫該方法,一旦方法通過了測試,就可以返回所有測試,確保我們新添加的代碼沒有任何預(yù)期外的副作用。一旦所有測試運行完畢(包括我們?yōu)樾鹿δ芫帉懙臏y試),就可以對我們的代碼進行檢查,并有理有據(jù)地相信程序行為符合我們的期望——當然,前提是我們的測試是適當?shù)摹?/p>
比如,我們編寫了一個函數(shù),該函數(shù)在特定的索引位置插入一個字符串,可以像下面這樣開始我們的TDD:
def insert_at(string, position, insert):
"""Returns a copy 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
對不返回任何參數(shù)的函數(shù)或方法(通常返回None),我們通常賦予其由pass構(gòu)成的一個suite,對那些返回值被試用的,我們或者返回一個常數(shù)(比如0),或者某個不變的參數(shù)——這也是我們這里所做的。(在更復雜的情況下,返回fake對象可能更有用一一對這樣的類,提供mock對象的第三方模塊是可用的。)
運行doctest時會失敗,并列出每個預(yù)期內(nèi)的字符串('ABCD-EF'、'ABCDE-F' 等),及其實際獲取的字符串(所有的都是'ABCD-EF')。一旦確定doctest是充分的和正確的,就可以編寫該函數(shù)的主體部分,在本例中只是簡單的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有些不同的方法,試圖盡可能消除樣板測試代碼。這兩個第三方模塊都支持測試發(fā)現(xiàn),因此沒必要寫一個總體的測試程序——因為模塊將自己搜索測試程序。這使得測試整個代碼樹或某一部分 (比如那些已經(jīng)起作用的模塊)變得很容易。那些對測試嚴重關(guān)切的人,在決定使用哪個測試工具之前,對這兩個(以及任何其他有吸引力的)第三方模塊進行研究都是值 得的。
創(chuàng)建doctest是直截了當?shù)模何覀冊谀K中編寫測試、函數(shù)、類與方法的docstrings。 對于模塊,我們簡單地在末尾添加了 3行:
if __name__ =="__main__":
import doctest
doctest.testmod()
在程序內(nèi)部使用doctest也是可能的。比如,blocks.py程序(其模塊在后面)有自己函數(shù)的doctest,但以如下代碼結(jié)尾:
if __name__== "__main__":
main()
這里簡單地調(diào)用了程序的main()函數(shù),并且沒有執(zhí)行程序的doctest。要實驗程序的 doctest,有兩種方法。一種是導入doctest模塊,之后運行程序---比如,在控制臺中輸 入 python3 -m doctest blocks.py (在 Wndows 平臺上,使用類似于 C:Python3 lpython.exe 這樣的形式替代python3)。如果所有測試運行良好,就沒有輸出,因此,我們可能寧愿執(zhí)行python3-m doctest blocks.py-v,因為這會列出每個執(zhí)行的doctest,并在最后給出結(jié)果摘要。
另一種執(zhí)行doctest的方法是使用unittest模塊創(chuàng)建單獨的測試程序。在概念上, unittest模塊是根據(jù)Java的JUnit單元測試庫進行建模的,并用于創(chuàng)建包含測試用例的測試套件。unittest模塊可以基于doctests創(chuàng)建測試用例,而不需要知道程序或模塊包含的任何事物——只要知道其包含doctest即可。因此,為給blocks.py程序制作一個測試套件,我們可以創(chuàng)建如下的簡單程序(將其稱為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標識符中,連接符是無效的(避開這一約束是可能的,但最簡單的解決方案是使用總是有效模塊名的程序文件名,比如,使用下劃線替換連接符)。這里展示的結(jié)構(gòu)(創(chuàng)建一個測試套件,添加一個或多個測試用例或測試套件,運行總體的測試套件,輸出結(jié)果)是典型的機遇unittest的測試。運行時,這一特定實例產(chǎn)生如下結(jié)果:
...
.............................................................................................................
Ran 3 tests in 0.244s
OK
每次執(zhí)行一個測試用例時,都會輸出一個句點(因此上面的輸出最前面有3個句點),之后是一行連接符,再之后是測試摘要(如果有任何一個測試失敗,就會有更多的輸出信息)。
如果我們嘗試將測試分離開(典型情況下是要測試的每個程序和模塊都有一個測試用例),就不要再使用doctests,而是直接使用unittest模塊的功能——尤其是我們習慣于使用JUnit方法進行測試時ounittest模塊會將測試分離于代碼——對大型項目(測試編寫人員與開發(fā)人員可能不一致)而言,這種方法特別有用。此外,unittest單元測試編寫為獨立的Python模塊,因此,不會像在docstring內(nèi)部編寫測試用例時受到兼容性和明智性的限制。
unittest模塊定義了 4個關(guān)鍵概念。測試夾具是一個用于描述創(chuàng)建測試(以及用完之后將其清理)所必需的代碼的術(shù)語,典型實例是創(chuàng)建測試所用的一個輸入文件,最后刪除輸入文件與結(jié)果輸出文件。測試套件是一組測試用例的組合。測試用例是測試的基本單元—我們很快就會看到實例。測試運行者是執(zhí)行一個或多個測試套件的對象。
典型情況下,測試套件是通過創(chuàng)建unittest.TestCase的子類實現(xiàn)的,其中每個名稱 以“test”開頭的方法都是一個測試用例。如果我們需要完成任何創(chuàng)建操作,就可以在一個名為setUp()的方法中實現(xiàn);類似地,對任何清理操作,也可以實現(xiàn)一個名為 tearDown()的方法。在測試內(nèi)部,有大量可供我們使用的unittest.TestCase方法,包括 assertTrue()、assertEqual()、assertAlmostEqual()(對于測試浮點數(shù)很有用)、assertRaises() 以及更多,還包括很多對應(yīng)的逆方法,比如assertFalse()、assertNotEqual()、failIfEqual()、 failUnlessEqual ()等。
unittest模塊進行了很好的歸檔,并且提供了大量功能,但在這里我們只是通過一 個非常簡單的測試套件來感受一下該模塊的使用。這里將要使用的實例,該練習要求創(chuàng)建一個Atomic模塊,該模塊可以用作一 個上下文管理器,以確?;蛘咚懈淖兌紤?yīng)用于某個列表、集合或字典,或者所有改變都不應(yīng)用。作為解決方案提供的Atomic.py模塊使用30行代碼來實現(xiàn)Atomic類, 并提供了 100行左右的模塊doctest。這里,我們將創(chuàng)建test_Atomic.py模塊,并使用 unittest測試替換doctest,以便可以刪除doctest。
在編寫測試模塊之前,我們需要思考都需要哪些測試。我們需要測試3種不同的數(shù)據(jù)類型:列表、集合與字典。對于列表,需要測試的是插入項、刪除項或修改項的值。對于集合,我們必須測試向其中添加或刪除一個項。對于字典,我們必須測試的是插入一個項、修改一個項的值、刪除一個項。此外,還必須要測試的是在失敗的情況下,不會有任何改變實際生效。
結(jié)構(gòu)上看,測試不同數(shù)據(jù)類型實質(zhì)上是一樣的,因此,我們將只為測試列表編寫測試用例,而將其他的留作練習。test_Atomic.py模塊必須導入unittest模塊與要進行測試的Atomic模塊。
創(chuàng)建unittest文件時,我們通常創(chuàng)建的是模塊而非程序。在每個模塊內(nèi)部,我們定義一個或多個unittest.TestCase子類。比如,test_Atomic.py模塊中僅一個單獨的 unittest-TestCase子類,也就是TestAtomic (稍后將對其進行講解),并以如下兩行結(jié)束:
if name == "__main__":
unittest.main()
這兩行使得該模塊可以單獨運行。當然,該模塊也可以被導入并從其他測試程序中運行——如果這只是多個測試套件中的一個,這一點是有意義的。
如果想要從其他測試程序中運行test_Atomic.py模塊,那么可以編寫一個與此類似的程序。我們習慣于使用unittest模塊執(zhí)行doctests,比如:
import unittest
import test_Atomic
suite = unittest.TestLoader().loadTestsFromTestCase(test_Atomic.TestAtomic)
runner = unittest.TextTestRunner()
pnnt(runner.run(suite))
這里,我們已經(jīng)創(chuàng)建了一個單獨的套件,這是通過讓unittest模塊讀取test_Atomic 模塊實現(xiàn)的,并且使用其每一個test*()方法(本實例中是test_list_success()、test_list_fail(),稍后很快就會看到)作為測試用例。
我們現(xiàn)在將查看TestAtomic類的實現(xiàn)。對通常的子類(不包括unittest.TestCase 子類),不怎么常見的是,沒有必要實現(xiàn)初始化程序。在這一案例中,我們將需要建立 一個方法,但不需要清理方法,并且我們將實現(xiàn)兩個測試用例。
def setUp(self):
self.original_list = list(range(10))
我們已經(jīng)使用了 unittest.TestCase.setUp()方法來創(chuàng)建單獨的測試數(shù)據(jù)片段。
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)
這里,我們直接在測試方法中編寫了測試代碼,而不需要一個內(nèi)部函數(shù),也不再使用unittest.TestCase.assertRaised()作為上下文管理器(期望代碼產(chǎn)生AttributeError)。 最后我們也使用了 Python 3.1 的 unittest.TestCase.assertListEqual()方法。
正如我們已經(jīng)看到的,Python的測試模塊易于使用,并且極為有用,在我們使用 TDD的情況下更是如此。它們還有比這里展示的要多得多的大量功能與特征——比如,跳過測試的能力,這有助于理解平臺差別——并且這些都有很好的文檔支持。缺失的一個功能——但nose與py.test提供了——是測試發(fā)現(xiàn),盡管這一特征被期望在后續(xù)的Python版本(或許與Python 3.2—起)中出現(xiàn)。
性能剖析(Profiling)
如果程序運行很慢,或者消耗了比預(yù)期內(nèi)要多得多的內(nèi)存,那么問題通常是選擇的算法或數(shù)據(jù)結(jié)構(gòu)不合適,或者是以低效的方式進行實現(xiàn)。不管問題的原因是什么, 最好的方法都是準確地找到問題發(fā)生的地方,而不只是檢査代碼并試圖對其進行優(yōu)化。 隨機優(yōu)化會導致引入bug,或者對程序中本來對程序整體性能并沒有實際影響的部分進行提速,而這并非解釋器耗費大部分時間的地方。
在深入討論profiling之前,注意一些易于學習和使用的Python程序設(shè)計習慣是有意義的,并且對提高程序性能不無裨益。這些技術(shù)都不是特定于某個Python版本的, 而是合理的Python程序設(shè)計風格。第一,在需要只讀序列時,最好使用元組而非列表; 第二,使用生成器,而不是創(chuàng)建大的元組和列表并在其上進行迭代處理;第三,盡量使用Python內(nèi)置的數(shù)據(jù)結(jié)構(gòu) dicts、lists、tuples 而不實現(xiàn)自己的自定義結(jié)構(gòu),因為內(nèi)置的數(shù)據(jù)結(jié)構(gòu)都是經(jīng)過了高度優(yōu)化的;第四,從小字符串中產(chǎn)生大字符串時, 不要對小字符串進行連接,而是在列表中累積,最后將字符串列表結(jié)合成為一個單獨的字符串;第五,也是最后一點,如果某個對象(包括函數(shù)或方法)需要多次使用屬性進行訪問(比如訪問模塊中的某個函數(shù)),或從某個數(shù)據(jù)結(jié)構(gòu)中進行訪問,那么較好的做法是創(chuàng)建并使用一個局部變量來訪問該對象,以便提供更快的訪問速度。
Python標準庫提供了兩個特別有用的模塊,可以輔助調(diào)査代碼的性能問題。一個是timeit模塊——該模塊可用于對一小段Python代碼進行計時,并可用于諸如對兩個或多個特定函數(shù)或方法的性能進行比較等場合。另一個是cProfile模塊,可用于profile 程序的性能——該模塊對調(diào)用計數(shù)與次數(shù)進行了詳細分解,以便發(fā)現(xiàn)性能瓶頸所在。
為了解timeit模塊,我們將查看一些小實例。假定有3個函數(shù)function_a()、 function_b()、function_c(), 3個函數(shù)執(zhí)行同樣的計算,但分別使用不同的算法。如果將這些函數(shù)放于同一個模塊中(或分別導入),就可以使用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()構(gòu)造子的第一個參數(shù)是我們想要執(zhí)行并計時的代碼,其形式是字符串。這里,該字符串是“function_a(X,Y)”;第二個參數(shù)是可選的,還是一個待執(zhí)行的字符串,這一次是在待計時的代碼之前,以便提供一些建立工作。這里,我們從 __main__ (即this)模塊導入了待測試的函數(shù),還有兩個作為輸入數(shù)據(jù)傳入的變量(X 與Y),這兩個變量在該模塊中是作為全局變量提供的。我們也可以很輕易地像從其他模塊中導入數(shù)據(jù)一樣來進行導入操作。
調(diào)用timeit.Timer對象的timeit()方法時,首先將執(zhí)行構(gòu)造子的第二個參數(shù)(如果有), 之后執(zhí)行構(gòu)造子的第一個參數(shù)并對其執(zhí)行時間進行計時。timeit.Timer.timeit()方法的返回值是以秒計數(shù)的時間,類型是float。默認情況下,timeit()方法重復100萬次,并返回所 有這些執(zhí)行的總秒數(shù),但在這一特定案例中,只需要1000次反復就可以給出有用的結(jié)果, 因此對重復計數(shù)次數(shù)進行了顯式指定。在對每個函數(shù)進行計時后,使用重復次數(shù)對總數(shù)進行除法操作,就得到了平均執(zhí)行時間,并在控制臺中打印出函數(shù)名與執(zhí)行時間。
function_a() 0.001618 sec
function_b() 0.012786 sec
function_c() 0.003248 sec
在這一實例中,function_a()顯然是最快的——至少對于這里使用的輸入數(shù)據(jù)而言。 在有些情況下一一比如輸入數(shù)據(jù)不同會對性能產(chǎn)生巨大影響——可能需要使用多組輸入數(shù)據(jù)對每個函數(shù)進行測試,以便覆蓋有代表性的測試用例,并對總執(zhí)行時間或平均執(zhí)行時間進行比較。
有時監(jiān)控自己的代碼進行計時并不是很方便,因此timeit模塊提供了一種在命令行中對代碼執(zhí)行時間進行計時的途徑。比如,要對MyModule.py模塊中的函數(shù)function_a()進行計時,可以在控制臺中輸入如下命令:python3 -m timeit -n 1000 -s "from MyModule import function_a, X, Y" "function_a(X, Y)"(與通常所做的一樣,對 Windows 環(huán)境,我們必須使用類似于C:Python3lpython.exe這樣的內(nèi)容來替換python3)。-m選項用于Python 解釋器,使其可以加載指定的模塊(這里是timeit),其他選項則由timeit模塊進行處理。 -n選項指定了循環(huán)計數(shù)次數(shù),-s選項指定了要建立,最后一個參數(shù)是要執(zhí)行和計時的代碼。命令完成后,會向控制臺中打印運行結(jié)果,比如:
1000 loops, best of 3: 1.41 msec per loop
之后我們可以輕易地對其他兩個函數(shù)進行計時,以便對其進行整體的比較。
cProfile模塊(或者profile模塊,這里統(tǒng)稱為cProfile模塊)也可以用于比較函數(shù) 與方法的性能。與只是提供原始計時的timeit模塊不同的是,cProfile模塊精確地展示 了有什么被調(diào)用以及每個調(diào)用耗費了多少時間。下面是用于比較與前面一樣的3個函數(shù)的代碼:
if __name__ == "__main__":
for function in ("function_a", "function_b", "function_c"):
cProfile.run("for i in ranged 1000): {0}(X, Y)".format(function))
我們必須將重復的次數(shù)放置在要傳遞給cProfile.run()函數(shù)的代碼內(nèi)部,但不需要做任何創(chuàng)建,因為模塊函數(shù)會使用內(nèi)省來尋找需要使用的函數(shù)與變量。這里沒有使用顯式的print()語句,因為默認情況下,cProfile.run()函數(shù)會在控制臺中打印其輸出。下面給出的是所有函數(shù)的相關(guān)結(jié)果(有些無關(guān)行被省略,格式也進行了稍許調(diào)整,以便與頁面適應(yīng)):
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 MyModule.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 MyModule.py:28(function_b)
5128000 7.048 0.000 7.048 0.000 MyModule.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 MyModule.py:36(function_c)
5128000 6.311 0.000 6.311 0.000 MyModule.py:37( )
1 0.000 0.000 12.987 12.987 {built-in method exec}
ncalls ("調(diào)用的次數(shù)")列列出了對指定函數(shù)(在filename:lineno(function)中列出) 的調(diào)用次數(shù)?;叵胍幌挛覀冎貜土?1000次調(diào)用,因此必須將這個次數(shù)記住。tottime (“總的時間”)列列出了某個函數(shù)中耗費的總時間,但是排除了函數(shù)調(diào)用的其他函數(shù)內(nèi)部花費的時間。第一個percall列列出了對函數(shù)的每次調(diào)用的平均時間(tottime // ncalls)。 cumtime ("累積時間")列出了在函數(shù)中耗費的時間,并且包含了函數(shù)調(diào)用的其他函數(shù)內(nèi)部花費的時間。第二個percall列列出了對函數(shù)的每次調(diào)用的平均時間,包括其調(diào)用的函數(shù)耗費的時間。
這種輸出信息要比timeit模塊的原始計時信息富有啟發(fā)意義的多。我們立即可以發(fā)現(xiàn),function_b()與function_c()使用了被調(diào)用5000次以上的生成器,使得它們的速度至少要比function_a()慢10倍以上。并且,function_b()調(diào)用了更多通常意義上的函數(shù),包括調(diào)用內(nèi)置的sorted()函數(shù),這使得其幾乎比function_c()還要慢兩倍。當然,timeit() 模塊提供了足夠的信息來查看計時上存在的這些差別,但cProfile模塊允許我們了解為什么會存在這些差別。正如timeit模塊允許對代碼進行計時而又不需要對其監(jiān)控一樣,cProfile模塊也可以做到這一點。然而,從命令行使用cProfile模塊時,我們不能精確地指定要執(zhí)行的 是什么——而只是執(zhí)行給定的程序或模塊,并報告所有這些的計時結(jié)果。需要使用的 命令行是python3 -m cProfile programOrModule.py,產(chǎn)生的輸出信息與前面看到的一 樣,下面給出的是輸出信息樣例,格式上進行了一些調(diào)整,并忽略了大多數(shù)行:
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術(shù)語學中,原始調(diào)用指的就是非遞歸的函數(shù)調(diào)用。
以這種方式使用cProfile模塊對于識別值得進一步研究的區(qū)域是有用的。比如,這里 我們可以清晰地看到function_b()需要耗費更長的時間,但是我們怎樣獲取進一步的詳細資料?我們可以使用cProfile.run("function_b()")來替換對function_b()的調(diào)用?;蛘呖梢员4嫱耆膒rofile數(shù)據(jù)并使用pstats模塊對其進行分析。要保存profile,就必須對命令行進行稍許修改:python3 -m cProfile -o profileDataFile programOrModule.py。 之后可以對 profile 數(shù)據(jù)進行分析,比如啟動IDLE,導入pstats模塊,賦予其已保存的profileDataFile,或者也可以在控制臺中交互式地使用pstats。
下面給出的是一個非常短的控制臺會話實例,為使其適合頁面展示,進行了適當調(diào)整,我們自己的輸入則以粗體展示:
$ python3 -m cProfile -o profile.dat MyModule.py
$ python3 -m pstats
Welcome to the profile statistics browser.
% read profile.dat
profile.dat% callers function_b
Random listing order was used
List reduced from 44 to 1 due 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 reduced from 44 to 1 due 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命令的參數(shù)。還有其他一些可用的工具,可以提供profile數(shù)據(jù)的圖形化展示形式,比如 RunSnakeRun (), 該工具需要依賴于wxPython GUI庫。
使用timeit與cProfile模塊,我們可以識別出我們自己代碼中哪些區(qū)域會耗費超過預(yù)期的時間;使用cProfile模塊,還可以準確算岀時間消耗在哪里。
以上內(nèi)容部分摘自視頻課程 05后端編程Python-19調(diào)試、測試和性能調(diào)優(yōu)(下) ,更多實操示例請參照視頻講解。跟著張員外講編程,學習更輕松,不花錢還能學習真本領(lǐng)。
通過官方網(wǎng)站介紹我們可以了解到,pytest是一個非常成熟的全功能的python測試框架,主要有
以下幾個特點:
1.直接使用pip命令安裝
2.驗證安裝結(jié)果
3.在pytest測試框架中,要遵循以下約束:
pytest進行測試比較簡單,我們來看一個實例:
這里我們定義了了兩個測試函數(shù),直接打印出結(jié)果,下面執(zhí)行測試:
輸出結(jié)果中顯示執(zhí)行了多少條案例、對應(yīng)的測試模塊、通過條數(shù)以及執(zhí)行耗時。
pytest斷言主要使用Python原生斷言方法,主要有以下幾種:
可以看到運行結(jié)果中明確指出了錯誤原因是"AssertionError",因為PHP不在str1中。
1.運行指定案例
2.運行當前文件夾包括子文件夾所有用例
3.運行指定文件夾(code目錄下所有用例)
4.運行模塊中指定用例(運行模塊中test_add用例)
5.執(zhí)行失敗的最大次數(shù)
使用表達式"--maxfail=num"來實現(xiàn)( 注意:表達式中間不能存在空格 ),表示用例失敗總數(shù)等于num 時停止運行。
6.錯誤信息在一行展示
在實際項目中如果有很多用例執(zhí)行失敗,查看報錯信息將會很麻煩。使用"--tb=line"命令,可以很好解決這個問題。
本地寫一個查詢用戶信息的接口,通過pytest來調(diào)用,并進行接口斷言。
使用mock,可以將某個函數(shù)所依賴的對象或者變量mock掉,從而降低測試條件的負責度。如下所示:
上述是mock對象的簡單使用方法,通過實例化一個Mock對象從而模擬掉原始函數(shù)的返回值,高級一些的用法就是通過mock.patch裝飾器,裝飾在類或者函數(shù)上進行模擬測試,如下在test.py文件中有兩個類:
測試用例設(shè)計如下:
以上測試用例說明,通過patch裝飾器模擬了 test.ProductionClass1 這個類,在 test_01 中使用 mock_class 模擬 test.ProductionClass1 。首先通過 mock_class.return_value 獲取類實例(如果模擬的是函數(shù),則不需要這一步),然后通過 obj1.pro1_method.return_value 設(shè)置方法的返回值,并進行測試。測試結(jié)果說明無論是通過 mock_class 還是 test.ProductionClass1 還是 obj1 執(zhí)行方法,獲取到的結(jié)果都是設(shè)置的值,并且在另一個類中調(diào)用模擬類的方法,也能成功獲取到設(shè)置的 return_value 。
目錄
pytest是Python的單元測試框架,同自帶的unittest框架類似,但pytest框架使用起來更簡潔,效率更高。
pytest特點
安裝
測試
在測試之前要做的準備
我的演示腳本處于這樣一個的目錄中:
踩坑:你創(chuàng)建的pytest腳本名稱中不允許含有 . ,比如 1.簡單上手.py ,這樣會報錯。當然,可以這么寫 1-簡單上手.py
demo1.py :
上例中,當我們在執(zhí)行(就像Python解釋器執(zhí)行普通的Python腳本一樣)測試用例的時候, pytest.main(["-s", "demo1.py"]) 中的傳參需要是一個元組或者列表(我的pytest是5.2.2版本),之前的版本可能需要這么調(diào)用 pytest.main("-s demo1.py") ,傳的參數(shù)是str的形式,至于你使用哪種,取決于報不報錯:
遇到上述報錯,就是參數(shù)需要一個列表或者元組的形式,而我們使用的是str形式。
上述代碼正確的執(zhí)行結(jié)果是這樣的:
大致的信息就是告訴我們:
pytest.main(["-s", "demo1.py"])參數(shù)說明
除了上述的函數(shù)這種寫法,也可以有用例類的寫法:
用法跟unittest差不多,類名要以 Test 開頭,并且其中的用例方法也要以 test 開頭,然后執(zhí)行也一樣。
執(zhí)行結(jié)果:
那么,你這個時候可能會問,我記得unittest中有setup和teardown的方法,難道pytest中沒有嘛?你怎么提都不提?穩(wěn)住,答案是有的。
接下來,我們來研究一下pytest中的setup和teardown的用法。
我們知道,在unittest中,setup和teardown可以在每個用例前后執(zhí)行,也可以在所有的用例集執(zhí)行前后執(zhí)行。那么在pytest中,有以下幾種情況:
來一一看看各自的用法。
模塊級別setup_module/teardown_module
執(zhí)行結(jié)果:
類級別的setup_class/teardown_class
執(zhí)行結(jié)果:
類中方法級別的setup_method/teardown_method
執(zhí)行結(jié)果:
函數(shù)級別的setup_function/teardown_function
執(zhí)行結(jié)果:
小結(jié)
該腳本有多種運行方式,如果處于PyCharm環(huán)境,可以使用右鍵或者點擊運行按鈕運行,也就是在pytest中的主函數(shù)中運行:
也可以在命令行中運行:
這種方式,跟使用Python解釋器執(zhí)行Python腳本沒有什么兩樣。也可以如下面這么執(zhí)行:
當然,還有一種是使用配置文件運行,來看看怎么用。
在項目的根目錄下,我們可以建立一個 pytest.ini 文件,在這個文件中,我們可以實現(xiàn)相關(guān)的配置:
那這個配置文件中的各項都是什么意思呢?
首先, pytest.ini 文件必須位于項目的根目錄,而且也必須叫做 pytest.ini 。
其他的參數(shù):
OK,來個示例。
首先,(詳細目錄參考開頭的目錄結(jié)構(gòu))在 scripts/test_case_01.py 中:
在 scripts/test_case_dir1/test_case02.py 中:
那么,在不同的目錄或者文件中,共有5個用例將被執(zhí)行,而結(jié)果則是兩個失敗三個成功。來執(zhí)行驗證一下,因為有了配置文件,我們在終端中(前提是在項目的根目錄),直接輸入 pytest 即可。
由執(zhí)行結(jié)果可以發(fā)現(xiàn), 2 failed, 3 passed ,跟我們的預(yù)期一致。
后續(xù)執(zhí)行相關(guān)配置都來自配置文件,如果更改,會有相應(yīng)說明,終端都是直接使用 pytest 執(zhí)行。
我們知道在unittest中,跳過用例可以用 skip ,那么這同樣是適用于pytest。
來看怎么使用:
跳過用例,我們使用 @pytest.mark.skipif(condition, reason) :
然后將它裝飾在需要被跳過用例的的函數(shù)上面。
效果如下:
上例執(zhí)行結(jié)果相對詳細,因為我們在配置文件中為 addopts 增加了 -v ,之前的示例結(jié)果中,沒有加!
另外,此時,在輸出的控制臺中, 還無法打印出 reason 信息,如果需要打印,則可以在配置文件中的 addopts 參數(shù)的 -s 變?yōu)?-rs :
如果我們事先知道測試函數(shù)會執(zhí)行失敗,但又不想直接跳過,而是希望顯示的提示。
Pytest 使用 pytest.mark.xfail 實現(xiàn)預(yù)見錯誤功能::
需要掌握的必傳參數(shù)的是:
那么關(guān)于預(yù)期失敗的幾種情況需要了解一下:
結(jié)果如下:
pytest 使用 x 表示預(yù)見的失?。╔FAIL)。
如果預(yù)見的是失敗,但實際運行測試卻成功通過,pytest 使用 X 進行標記(XPASS)。
而在預(yù)期失敗的兩種情況中,我們不希望出現(xiàn)預(yù)期失敗,結(jié)果卻執(zhí)行成功了的情況出現(xiàn),因為跟我們想的不一樣嘛,我預(yù)期這條用例失敗,那這條用例就應(yīng)該執(zhí)行失敗才對,你雖然執(zhí)行成功了,但跟我想的不一樣,你照樣是失敗的!
所以,我們需要將預(yù)期失敗,結(jié)果卻執(zhí)行成功了的用例標記為執(zhí)行失敗,可以在 pytest.ini 文件中,加入:
這樣就就把上述的情況標記為執(zhí)行失敗了。
pytest身為強大的單元測試框架,那么同樣支持DDT數(shù)據(jù)驅(qū)動測試的概念。也就是當對一個測試函數(shù)進行測試時,通常會給函數(shù)傳遞多組參數(shù)。比如測試賬號登陸,我們需要模擬各種千奇百怪的賬號密碼。
當然,我們可以把這些參數(shù)寫在測試函數(shù)內(nèi)部進行遍歷。不過雖然參數(shù)眾多,但仍然是一個測試,當某組參數(shù)導致斷言失敗,測試也就終止了。
通過異常捕獲,我們可以保證程所有參數(shù)完整執(zhí)行,但要分析測試結(jié)果就需要做不少額外的工作。
在 pytest 中,我們有更好的解決方法,就是參數(shù)化測試,即每組參數(shù)都獨立執(zhí)行一次測試。使用的工具就是 pytest.mark.parametrize(argnames, argvalues) 。
使用就是以裝飾器的形式使用。
只有一個參數(shù)的測試用例
來看(重要部分)結(jié)果::
可以看到,列表內(nèi)的每個手機號,都是一條測試用例。
多個參數(shù)的測試用例
(重要部分)結(jié)果:
可以看到,每一個手機號與每一個驗證碼都組合一起執(zhí)行了,這樣就執(zhí)行了4次。那么如果有很多個組合的話,用例數(shù)將會更多。我們希望手機號與驗證碼一一對應(yīng)組合,也就是只執(zhí)行兩次,怎么搞呢?
在多參數(shù)情況下,多個參數(shù)名是以 , 分割的字符串。參數(shù)值是列表嵌套的形式組成的。
固件(Fixture)是一些函數(shù),pytest 會在執(zhí)行測試函數(shù)之前(或之后)加載運行它們,也稱測試夾具。
我們可以利用固件做任何事情,其中最常見的可能就是數(shù)據(jù)庫的初始連接和最后關(guān)閉操作。
Pytest 使用 pytest.fixture() 定義固件,下面是最簡單的固件,訪問主頁前必須先登錄:
結(jié)果:
在之前的示例中,你可能會覺得,這跟之前的setup和teardown的功能也類似呀,但是,fixture相對于setup和teardown來說更靈活。pytest通過 scope 參數(shù)來控制固件的使用范圍,也就是作用域。
比如之前的login固件,可以指定它的作用域:
很多時候需要在測試前進行預(yù)處理(如新建數(shù)據(jù)庫連接),并在測試完成進行清理(關(guān)閉數(shù)據(jù)庫連接)。
當有大量重復的這類操作,最佳實踐是使用固件來自動化所有預(yù)處理和后處理。
Pytest 使用 yield 關(guān)鍵詞將固件分為兩部分, yield 之前的代碼屬于預(yù)處理,會在測試前執(zhí)行; yield 之后的代碼屬于后處理,將在測試完成后執(zhí)行。
以下測試模擬數(shù)據(jù)庫查詢,使用固件來模擬數(shù)據(jù)庫的連接關(guān)閉:
結(jié)果:
可以看到在兩個測試用例執(zhí)行前后都有預(yù)處理和后處理。
pytest中還有非常多的插件供我們使用,我們來介紹幾個常用的。
先來看一個重要的,那就是生成測試用例報告。
想要生成測試報告,首先要有下載,才能使用。
下載
如果下載失敗,可以使用PyCharm下載,怎么用PyCharm下載這里無需多言了吧。
使用
在配置文件中,添加參數(shù):
效果很不錯吧!
沒完,看我大招
Allure框架是一個靈活的輕量級多語言測試報告工具,它不僅以web的方式展示了簡潔的測試結(jié)果,而且允許參與開發(fā)過程的每個人從日常執(zhí)行的測試中最大限度的提取有用信息。
從開發(fā)人員(dev,developer)和質(zhì)量保證人員(QA,Quality Assurance)的角度來看,Allure報告簡化了常見缺陷的統(tǒng)計:失敗的測試可以分為bug和被中斷的測試,還可以配置日志、步驟、fixture、附件、計時、執(zhí)行 歷史 以及與TMS和BUG管理系統(tǒng)集成,所以,通過以上配置,所有負責的開發(fā)人員和測試人員可以盡可能的掌握測試信息。
從管理者的角度來看,Allure提供了一個清晰的“大圖”,其中包括已覆蓋的特性、缺陷聚集的位置、執(zhí)行時間軸的外觀以及許多其他方便的事情。allure的模塊化和可擴展性保證了我們總是能夠?qū)δ承〇|西進行微調(diào)。
少扯點,來看看怎么使用。
Python的pytest中allure下載
但由于這個 allure-pytest 插件生成的測試報告不是 html 類型的,我們還需要使用allure工具再“加工”一下。所以說,我們還需要下載這個allure工具。
allure工具下載
在現(xiàn)在allure工具之前,它依賴Java環(huán)境,我們還需要先配置Java環(huán)境。
注意,如果你的電腦已經(jīng)有了Java環(huán)境,就無需重新配置了。
配置完了Java環(huán)境,我們再來下載allure工具,我這里直接給出了百度云盤鏈接,你也可以去其他鏈接中自行下載:
下載并解壓好了allure工具包之后,還需要將allure包內(nèi)的 bin 目錄添加到系統(tǒng)的環(huán)境變量中。
完事后打開你的終端測試:
返回了版本號說明安裝成功。
使用
一般使用allure要經(jīng)歷幾個步驟:
來看配置 pytest.ini :
就是 --alluredir ./report/result 參數(shù)。
在終端中輸入 pytest 正常執(zhí)行測試用例即可:
執(zhí)行完畢后,在項目的根目下,會自動生成一個 report 目錄,這個目錄下有:
接下來需要使用allure工具來生成HTML報告。
此時我們在終端(如果是windows平臺,就是cmd),路徑是項目的根目錄,執(zhí)行下面的命令。
PS:我在pycharm中的terminal輸入allure提示'allure' 不是內(nèi)部或外部命令,也不是可運行的程序或批處理文件。但windows的終端沒有問題。
命令的意思是,根據(jù) reportresult 目錄中的數(shù)據(jù)(這些數(shù)據(jù)是運行pytest后產(chǎn)生的)。在 report 目錄下新建一個 allure_html 目錄,而這個目錄內(nèi)有 index.html 才是最終的allure版本的HTML報告;如果你是重復執(zhí)行的話,使用 --clean 清除之前的報告。
結(jié)果很漂亮:
allure open
默認的,allure報告需要HTTP服務(wù)器來打開,一般我們可以通過pycharm來完成,另外一種情況就是通過allure自帶的open命令來完成。
allure的其他用法
當然,故事還是沒有完!在使用allure生成報告的時候,在編寫用例階段,還可以有一些參數(shù)可以使用:
allure.title與allure.description
feature和story
由上圖可以看到,不同的用例被分為不同的功能中。
allure.severity
allure.severity 用來標識測試用例或者測試類的級別,分為blocker,critical,normal,minor,trivial5個級別。
severity的默認級別是normal,所以上面的用例5可以不添加裝飾器了。
allure.dynamic
在之前,用例的執(zhí)行順序是從上到下依次執(zhí)行:
正如上例的執(zhí)行順序是 3 1 2 。
現(xiàn)在,來看看我們?nèi)绾问謩涌刂贫鄠€用例的執(zhí)行順序,這里也依賴一個插件。
下載
使用
手動控制用例執(zhí)行順序的方法是在給各用例添加一個裝飾器:
那么, 現(xiàn)在的執(zhí)行順序是 2 1 3 ,按照order指定的排序執(zhí)行的。
如果有人較勁傳個0或者負數(shù)啥的,那么它們的排序關(guān)系應(yīng)該是這樣的:
失敗重試意思是指定某個用例執(zhí)行失敗可以重新運行。
下載
使用
需要在 pytest.ini 文件中, 配置:
給 addopts 字段新增(其他原有保持不變) --reruns=3 字段,這樣如果有用例執(zhí)行失敗,則再次執(zhí)行,嘗試3次。
來看示例:
結(jié)果:
我們也可以從用例報告中看出重試的結(jié)果:
上面演示了用例失敗了,然后重新執(zhí)行多少次都沒有成功,這是一種情況。
接下來,來看另一種情況,那就是用例執(zhí)行失敗,重新執(zhí)行次數(shù)內(nèi)通過了,那么剩余的重新執(zhí)行的次數(shù)將不再執(zhí)行。
通過 random 模塊幫助我們演示出在某次執(zhí)行中出現(xiàn)失敗的情況,而在重新執(zhí)行的時候,會出現(xiàn)成功的情況,看結(jié)果:
可以看到,用例 02 重新執(zhí)行了一次就成功了,剩余的兩次執(zhí)行就終止了。
一條一條用例的執(zhí)行,肯定會很慢,來看如何并發(fā)的執(zhí)行測試用例,當然這需要相應(yīng)的插件。
下載
使用
在配置文件中添加:
就是這個 -n=auto :
并發(fā)的配置可以寫在配置文件中,然后其他正常的執(zhí)行用例腳本即可。另外一種就是在終端中指定,先來看示例:
結(jié)果:
pytest-sugar 改變了 pytest 的默認外觀,添加了一個進度條,并立即顯示失敗的測試。它不需要配置,只需 下載插件即可,用 pytest 運行測試,來享受更漂亮、更有用的輸出。
下載
其他照舊執(zhí)行用例即可。
pytest-cov 在 pytest 中增加了覆蓋率支持,來顯示哪些代碼行已經(jīng)測試過,哪些還沒有。它還將包括項目的測試覆蓋率。
下載
使用
在配置文件中:
也就是配置 --cov=./scripts ,這樣,它就會統(tǒng)計所有 scripts 目錄下所有符合規(guī)則的腳本的測試覆蓋率。
執(zhí)行的話,就照常執(zhí)行就行。
結(jié)果:
更多插件參考:
有的時候,在 pytest.ini 中配置了 pytest-html 和 allure 插件之后,執(zhí)行后報錯:
出現(xiàn)了這個報錯,檢查你配置的解釋器中是否存在 pytest-html 和 allure-pytest 這兩個模塊。如果是使用的pycharm ide,那么你除了檢查settings中的解釋器配置之外,還需要保證運行腳本的編輯器配置是否跟settings中配置一致。