首頁 >後端開發 >Python教學 >身為Python程式設計師必須要會的開發者工具

身為Python程式設計師必須要會的開發者工具

巴扎黑
巴扎黑原創
2017-04-08 10:34:561796瀏覽

  Python已經演化了一個廣泛的生態系統,該生態系統能夠讓Python程式設計師的生活變得更加簡單,減少他們重複造輪的工作。同樣的理念也適用於工具開發者的工作,即便他們開發出的工具並沒有出現在最終的程式中。本文將介紹Python程式設計師必知必會的開發者工具。

#   對於開發者來說,最實用的幫助莫過於幫助他們編寫程式碼文件了。 pydoc模組可以根據原始程式碼中的docstrings為任何可導入模組產生格式良好的文件。 Python包含了兩個測試框架來自動測試程式碼以及驗證程式碼的正確性:1)doctest#模組,該模組可以從原始程式碼或獨立檔案的範例中抽取測試用例。 2)unittest模組,該模組是一個全功能的自動化測試框架,該框架提供了對測試準備(test fixtures), 預定義測試集(predefined test suite)以及測試發現(test discovery)的支援。

  trace模組可以監控Python執行程式的方式,同時產生一個報表來顯示程式的每一行執行的次數。這些資訊可以用來發現未被自動化測試集所覆蓋的程式執行路徑,也可以用來研究程式呼叫圖,進而發現模組之間的依賴關係。編寫並執行測試可以發現絕大多數程式中的問題,Python使得debug工作變得更加簡單,這是因為在大部分情況下,Python都能夠將未處理的錯誤列印到控制台中,我們稱這些錯誤資訊為traceback。如果程式不是在文字控制台中執行的,traceback也能夠將錯誤訊息輸出到日誌檔案或是訊息對話方塊中。當標準的traceback無法提供足夠的資訊時,可以使用cgitb 模組來查看各級堆疊和原始程式碼上下文中的詳細信息,例如局部變數。 cgitb模組也能夠將這些追蹤資訊以HTML的形式輸出,用來報告web應用程式中的錯誤。

  一旦發現了問題出在哪裡後,就需要使用到交互式調試器進入到程式碼中進行調試工作了,pdb模組能夠很好地勝任這項工作。此模組可以顯示出程式在錯誤產生時的執行路徑,同時可以動態地調整物件和程式碼進行偵錯。當程式通過測試並調試後,下一步就是要將注意力放到效能上了。開發者可以使用profile以及timit模組來測試程式的速度,找出程式中到底是哪裡很慢,進而對這部分程式碼獨立出來進行調優的工作。 Python程式是透過解譯器執行的,解譯器的輸入是原有程式的字節碼編譯版本。這個字節碼編譯版本可以在程式執行時動態地生成,也可以在程式打包的時候就生成。 compileall模組可以處理程式打包的事宜,它暴露出了打包相關的接口,該接口能夠被安裝程式和打包工具用來生成包含模組字節碼的檔案。同時,在開發環境中,compileall模組也可以用來驗證原始檔是否包含了語法錯誤。

  在原始碼級別,pyclbr模組提供了一個類別檢視器,方便文字編輯器或是其他程式對Python程式中有意思的字元進行掃描,例如函數或者是類別。在提供了類別檢視器以後,就無需引入程式碼,這樣就避免了潛在的副作用影響。

 文件字串與doctest模組

#   如果函數,類別或是模組的第一行是一個字串,那麼這個字串就是一個文件字串。可以認為包含文件字串是一個良好的程式設計習慣,這是因為這些字串可以給Python程式開發工具提供一些資訊。例如,help()指令能夠偵測文件字串,Python相關的IDE也能夠進行偵測文件字串的工作。由於程式設計師傾向於在互動式shell中查看文件字串,所以最好將這些字串寫的簡短一些。例如

# mult.py
class Test:
    """
    >>> a=Test(5)
    >>> a.multiply_by_2()
    10
    """
    def __init__(self, number):
        self._number=number

    def multiply_by_2(self):
        return self._number*2

  在撰寫文件時,一個常見的問題就是如何保持文件和實際程式碼的同步。例如,程式設計師也許會修改函數的實現,但是卻忘記了更新文件。針對這個問題,我們可以使用doctest模組。 doctest模組會收集文件字串,並對它們進行掃描,然後將它們作為測試進行執行。為了使用doctest模組,我們通常會新建一個用於測試的獨立的模組。例如,如果前面的範例Test class包含在檔案mult.py中,那麼,你應該新建一個testmult.py檔案用來測試,如下所示:

# testmult.py

import mult, doctest

doctest.testmod(mult, verbose=True)

# Trying:
#     a=Test(5)
# Expecting nothing
# ok
# Trying:
#     a.multiply_by_2()
# Expecting:
#     10
# ok
# 3 items had no tests:
#     mult
#     mult.Test.__init__
#     mult.Test.multiply_by_2
# 1 items passed all tests:
#    2 tests in mult.Test
# 2 tests in 4 items.
# 2 passed and 0 failed.
# Test passed.

  在這段程式碼中,doctest.testmod(module)會執行特定模組的測試,並且傳回測試失敗的數量以及測試的總數。如果所有的測試都通過了,那麼就不會產生任何輸出。否則的話,你將會看到一個失敗報告,用來顯示期望值和實際值之間的差異。如果你想看到測試的詳細輸出,你可以使用testmod(module, verbose=True).

  如果不想新建一個單獨的測試檔案的話,那麼另一個選擇就是在檔案結尾包含對應的測試程式碼:

if __name__ == '__main__':
    import doctest
    doctest.testmod()

  如果想執行這類測試的話,我們可以透過-m選項呼叫doctest模組。通常來講,執行測試的時候沒有任何的輸出。如果想查看詳細資訊的話,可以加上-v選項。

$ python -m doctest -v mult.py

 單元測試與unittest模組

#   如果想更徹底地對程式進行測試,我們可以使用unittest模組。透過單元測試,開發者可以為構成程式的每一個元素(例如,獨立的函數,方法,類別以及模組)編寫一系列獨立的測試案例。當測試更大的程序時,這些測試可以作為基石來驗證程序的正確性。當我們的程式變得越來越大的時候,不同構件的單元測試就可以組合起來成為更大的測試框架以及測試工具。這能夠大大簡化軟體測試的工作,為找到並解決軟體問題提供了便利。

# splitter.py
import unittest

def split(line, types=None, delimiter=None):
    """Splits a line of text and optionally performs type conversion.
    ...
    """
    fields = line.split(delimiter)
    if types:
        fields = [ ty(val) for ty,val in zip(types,fields) ]
    return fields

class TestSplitFunction(unittest.TestCase):
    def setUp(self):
        # Perform set up actions (if any)
        pass
    def tearDown(self):
        # Perform clean-up actions (if any)
        pass
    def testsimplestring(self):
        r = split('GOOG 100 490.50')
        self.assertEqual(r,['GOOG','100','490.50'])
    def testtypeconvert(self):
        r = split('GOOG 100 490.50',[str, int, float])
        self.assertEqual(r,['GOOG', 100, 490.5])
    def testdelimiter(self):
        r = split('GOOG,100,490.50',delimiter=',')
        self.assertEqual(r,['GOOG','100','490.50'])

# Run the unittests
if __name__ == '__main__':
    unittest.main()

#...
#----------------------------------------------------------------------
#Ran 3 tests in 0.001s

#OK

  在使用單元測試時,我們需要定義一個繼承自unittest.TestCase的類別。在這個類別裡面,每一個測試都以方法的形式進行定義,並且都以test打頭進行命名——例如,'testsimplestring','testtypeconvert# #'以及類似的命名方式(有必要強調一下,只要方法名稱以test打頭,那麼無論怎麼命名都是可以的)。在每個測試中,斷言可以用來對不同的條件進行檢查。

  實際的例子:

#   假如你在程式裡有一個方法,這個方法的輸出指向標準輸出(sys.stdout)。這通常意味著是往螢幕上輸出文字訊息。如果你想對你的程式碼進行測試來證明這一點,只要給出相應的輸入,那麼對應的輸出就會被顯示出來。

# url.py

def urlprint(protocol, host, domain):
    url = '{}://{}.{}'.format(protocol, host, domain)
    print(url)

  內建的print函數在預設情況下會往sys.stdout發送輸出。為了測試輸出已經實際到達,你可以使用一個替身物件對其進行模擬,並且對程式的期望值進行斷言。 unittest.mock模組中的patch()方法可以只在運行測試的上下文中才替換對象,在測試完成後就立刻返回對象原始的狀態。下面是urlprint()方法的測試程式碼:

#urltest.py

from io import StringIO
from unittest import TestCase
from unittest.mock import patch
import url

class TestURLPrint(TestCase):
    def test_url_gets_to_stdout(self):
        protocol = 'http'
        host = 'www'
        domain = 'example.com'
        expected_url = '{}://{}.{}\n'.format(protocol, host, domain)

        with patch('sys.stdout', new=StringIO()) as fake_out:
            url.urlprint(protocol, host, domain)
            self.assertEqual(fake_out.getvalue(), expected_url)

  urlprint()函数有三个参数,测试代码首先给每个参数赋了一个假值。变量expected_url包含了期望的输出字符串。为了能够执行测试,我们使用了unittest.mock.patch()方法作为上下文管理器,把标准输出sys.stdout替换为了StringIO对象,这样发送的标准输出的内容就会被StringIO对象所接收。变量fake_out就是在这一过程中所创建出的模拟对象,该对象能够在with所处的代码块中所使用,来进行一系列的测试检查。当with语句完成时,patch方法能够将所有的东西都复原到测试执行之前的状态,就好像测试没有执行一样,而这无需任何额外的工作。但对于某些Python的C扩展来讲,这个例子却显得毫无意义,这是因为这些C扩展程序绕过了sys.stdout的设置,直接将输出发送到了标准输出上。这个例子仅适用于纯Python代码的程序(如果你想捕获到类似C扩展的输入输出,那么你可以通过打开一个临时文件然后将标准输出重定向到该文件的技巧来进行实现)。

以上是身為Python程式設計師必須要會的開發者工具的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn