翻譯:《實用的Python編程》08_01_Testing

目錄 | 上一節 (7.5 裝飾方法 | 下一節 (8.2 日誌)html

8.1 測試

多測試,少調試(Testing Rocks, Debugging Sucks)

Python 的動態性質使得測試對大多數程序而言相當重要。編譯器不會發現你的 bug,發現 bug 的惟一方式是運行代碼,並確保嘗試了全部的特性。python

斷言(Assertions)

assert 語句用於程序的內部檢查。若是表達式不爲真,則會觸發 AssertionError 異常。git

assert 語句語法:程序員

assert <expression> [, 'Diagnostic message']

示例:github

assert isinstance(10, int), 'Expected int'

assert 語句不該用於檢查用戶的輸入(例如,在網頁表單輸入的數據)。 assert 語句旨在用於內部檢查或者用於不變量(invariant,始終爲 True 的條件)。express

契約式編程

契約式編程(contract programming)也稱爲契約式設計(Design By Contract),自由使用斷言是一種軟件設計方法。契約式編程規定軟件設計人員應該爲軟件組件定義精確的接口規範。編程

例如,你能夠在全部的函數輸入中使用斷言:bash

def add(x, y):
    assert isinstance(x, int), 'Expected int'
    assert isinstance(y, int), 'Expected int'
    return x + y

若是函數調用者沒有使用正確的參數,那麼檢查輸入能夠當即捕捉到。函數

>>> add(2, 3)
5
>>> add('2', '3')
Traceback (most recent call last):
...
AssertionError: Expected int
>>>

內聯測試

斷言也能夠用於簡單的測試。工具

def add(x, y):
    return x + y

assert add(2,2) == 4

這樣,你就能夠將測試與代碼包含在同一模塊中。

好處:若是代碼明顯被破壞,那麼嘗試導入模塊將會致使程序崩潰。

對於詳盡的測試,不推薦這樣作。這種作法更像是基本的「冒煙測試(smoke test)」。函數是否能夠在全部的用例上正常工做?若是不能夠,那麼確定是有問題的。

unittest 模塊

假設你有下面這樣一段代碼:

# simple.py

def add(x, y):
    return x + y

如今,你想對這些代碼進行測試,請建立一個單獨的測試文件,以下所示:

# test_simple.py

import simple
import unittest

而後定義一個測試類:

# test_simple.py

import simple
import unittest

# Notice that it inherits from unittest.TestCase
class TestAdd(unittest.TestCase):
    ...

測試類必須繼承自unittest.TestCase

在測試類中,定義測試方法:

# test_simple.py

import simple
import unittest

# Notice that it inherits from unittest.TestCase
class TestAdd(unittest.TestCase):
    def test_simple(self):
        # Test with simple integer arguments
        r = simple.add(2, 2)
        self.assertEqual(r, 5)
    def test_str(self):
        # Test with strings
        r = simple.add('hello', 'world')
        self.assertEqual(r, 'helloworld')

重要提示:每一個方法的名稱必須以 test 開頭。

使用 unittest

unittest 中內置了一些斷言,每種斷言對不一樣的事情進行診斷。

# Assert that expr is True
self.assertTrue(expr)

# Assert that x == y
self.assertEqual(x,y)

# Assert that x != y
self.assertNotEqual(x,y)

# Assert that x is near y
self.assertAlmostEqual(x,y,places)

# Assert that callable(arg1,arg2,...) raises exc
self.assertRaises(exc, callable, arg1, arg2, ...)

上述列表並非一個完整的列表,unittest 模塊還有其它斷言。

運行 unittest

要運行測試,請把代碼轉換爲腳本。

# test_simple.py

...

if __name__ == '__main__':
    unittest.main()

而後使用 Python 執行測試文件:

bash % python3 test_simple.py
F.
========================================================
FAIL: test_simple (__main__.TestAdd)
--------------------------------------------------------
Traceback (most recent call last):
  File "testsimple.py", line 8, in test_simple
    self.assertEqual(r, 5)
AssertionError: 4 != 5
--------------------------------------------------------
Ran 2 tests in 0.000s
FAILED (failures=1)

說明

高效的單元測試是一種藝術。對於大型應用而言,單元測試可能會變得很是複雜。

unittest 模塊具備大量與測試運行器(test runners),測試結果集(collection of results)以及測試其餘方面相關的選項。相關詳細信息,請查閱文檔。

第三方測試工具

雖然內置 unittest 模塊的優點是能夠隨處使用——由於它是 Python 的一部分,可是許多程序員也以爲 unittest 很是繁瑣。另外一個流行的的測試工具是 pytest。使用 pytest,測試文件能夠簡化爲如下形式:

# test_simple.py
import simple

def test_simple():
    assert simple.add(2,2) == 4

def test_str():
    assert simple.add('hello','world') == 'helloworld'

要運行測試,只須要輸入一個命令便可,例如:python -m pytest 。它將會發現全部的測試並運行這些測試。

除了這個示例以外,pytest 還有不少內容。若是你決定嘗試一下,一般很容易上手。

練習

在本次練習中,咱們將探索使用 Python unittest 模塊的基本機制(mechanics)。

在前面的練習中,咱們編寫了一個包含 Stock 類的 stock.py 文件。對於本次練習,假設咱們使用的是 練習7.9 中編寫的與類型化屬性相關的代碼(譯註:typedproperty.py)。若是由於某些緣由,練習 7.9 的代碼沒法正常工做,你能夠從 Solutions/7_9 中複製 typedproperty.py 到工做目錄中。

練習 8.1:編寫單元測試

請建立一個單獨的 test_stock.py 文件,爲 Stock 編寫單元測試集。爲了讓你入門,這裏有一小段測試實例建立的代碼:

# test_stock.py

import unittest
import stock

class TestStock(unittest.TestCase):
    def test_create(self):
        s = stock.Stock('GOOG', 100, 490.1)
        self.assertEqual(s.name, 'GOOG')
        self.assertEqual(s.shares, 100)
        self.assertEqual(s.price, 490.1)

if __name__ == '__main__':
    unittest.main()

運行單元測試,你應該能夠得到一些像下面這有的輸出:

.
----------------------------------------------------------------------
Ran 1 tests in 0.000s

OK

而後,編寫其它單元測試來檢查如下各項內容:

  • 確保 s.cost 屬性返回正確的值(49010.0)。
  • 確保 s.sell() 方法正常工做。它應該相應地減少 s.shares
  • 確保 s.shares 屬性只能設置爲整數值。

對於最後一部分,你須要檢查異常的觸發。要作到這些,一種簡單的方法是使用以下代碼:

class TestStock(unittest.TestCase):
    ...
    def test_bad_shares(self):
         s = stock.Stock('GOOG', 100, 490.1)
         with self.assertRaises(TypeError):
             s.shares = '100'

目錄 | 上一節 (7.5 裝飾方法 | 下一節 (8.2 日誌)

注:完整翻譯見 https://github.com/codists/practical-python-zh

相關文章
相關標籤/搜索