天天看點

Python之學會測試,讓開發更加高效(一)-- coding: utf-8 ---- coding: utf-8 --mock_multipy.py-- coding: utf-8 --test Operator class-- coding: utf-8 --test Operator class-- coding: utf-8 --mock_multipy.py-- coding: utf-8 --test Operator class

Python之學會測試,讓開發更加高效(一)

前幾天,聽了公司某位大佬關于程式設計心得的體會,其中講到了“測試驅動開發”,感覺自己的測試技能薄弱,是以,寫下這篇文章,希望對測試能有個入門。這段時間,筆者也體會到了測試的價值,一句話,學會測試,能夠讓你的開發更加高效。

本文将介紹以下兩個方面的内容:

Test with Coverage

Mock

測試覆寫率通常被用來衡量測試的充分性和完整性。從廣義的角度講,主要分為兩大類:面向項目的需求覆寫率和更偏向技術的代碼覆寫率。對于開發人員來說,我們更注重代碼覆寫率。

代碼覆寫率指的是至少執行了一次的條目數占整個條目數的百分比。如果條目數是語句,對應的就是代碼行覆寫率;如果條目數是函數,對應的就是函數覆寫率;如果條目數是路徑,對應的就是路徑覆寫率,等等。統計代碼覆寫率的根本目的是找出潛在的遺漏測試用例,并有針對性的進行補充,同時還可以識别出代碼中那些由于需求變更等原因造成的廢棄代碼。通常我們希望代碼覆寫率越高越好,代碼覆寫率越高越能說明你的測試用例設計是充分且完備的,但測試的成本會随着代碼覆寫率的提高而增加。

在Python中,coverage子產品幫助我們實作了代碼行覆寫率,我們可以友善地使用它來完整測試的代碼行覆寫率。

我們通過一個例子來介紹coverage子產品的使用。

首先,我們有腳本func_add.py,實作了add函數,代碼如下:

-- coding: utf-8 --

def add(a, b):

if isinstance(a, str) and isinstance(b, str):
    return a + '+' + b
elif isinstance(a, list) and isinstance(b, list):
    return a + b
elif isinstance(a, (int, float)) and isinstance(b, (int, float)):
    return a + b
else:
    return None           

在add函數中,分四種情況實作了加法,分别是字元串,清單,屬性值,以及其它情況。

接着,我們用unittest子產品來進行單元測試,代碼腳本(test_func_add.py)如下:

import unittest

from func_add import add

class Test_Add(unittest.TestCase):

def setUp(self):
    pass

def test_add_case1(self):
    a = "Hello"
    b = "World"
    res = add(a, b)
    print(res)
    self.assertEqual(res, "Hello+World")

def test_add_case2(self):
    a = 1
    b = 2
    res = add(a, b)
    print(res)
    self.assertEqual(res, 3)

def test_add_case3(self):
    a = [1, 2]
    b = [3]
    res = add(a, b)
    print(res)
    self.assertEqual(res, [1, 2, 3])

def test_add_case4(self):
    a = 2
    b = "3"
    res = add(a, b)
    print(None)
    self.assertEqual(res, None)
           

if name == '__main__':

# 部分用例測試
# 構造一個容器用來存放我們的測試用例
suite = unittest.TestSuite()
# 添加類中的測試用例
suite.addTest(Test_Add('test_add_case1'))
suite.addTest(Test_Add('test_add_case2'))
# suite.addTest(Test_Add('test_add_case3'))
# suite.addTest(Test_Add('test_add_case4'))
run = unittest.TextTestRunner()
run.run(suite)           

在這個測試中,我們隻測試了前兩個用例,也就是對字元串和數值型的加法進行測試。

在指令行中輸入coverage run test_func_add.py指令運作該測試腳本,輸出結果如下:

Hello+World

.3

.

Ran 2 tests in 0.000s

OK

再輸入指令coverage html就能生成代碼行覆寫率的報告,會生成htmlcov檔案夾,打開其中的index.html檔案,就能看到本次執行的覆寫率情況,如下圖:

我們點選func_add.py檢視add函數測試的情況,如下圖:

可以看到,單元測試腳本test_func_add.py的前兩個測試用例隻覆寫到了add函數中左邊綠色的部分,而沒有測試到紅色的部分,代碼行覆寫率為75%。

是以,還有兩種情況沒有覆寫到,說明我們的單元測試中的測試用例還不夠充分。

在test_func_add.py中,我們把main函數中的注釋去掉,把後兩個測試用例也添加進來,這時候我們再運作上面的coverage子產品的指令,重新生成htmlcov後,func_add.py的代碼行覆寫率如下圖:

可以看到,增加測試用例後,我們調用的add函數代碼行覆寫率為100%,所有的代碼都覆寫到了。

Mock這個詞在英語中有模拟的這個意思,是以我們可以猜測出這個庫的主要功能是模拟一些東西。準确的說,Mock是Python中一個用于支援單元測試的庫,它的主要功能是使用mock對象替代掉指定的Python對象,以達到模拟對象的行為。在Python3中,mock是輔助單元測試的一個子產品。它允許您用模拟對象替換您的系統的部分,并對它們已使用的方式進行斷言。

在實際生産中的項目是非常複雜的,對其進行單元測試的時候,會遇到以下問題:

接口的依賴

外部接口調用

測試環境非常複雜

單元測試應該隻針對目前單元進行測試, 所有的内部或外部的依賴應該是穩定的, 已經在别處進行測試過的。使用mock 就可以對外部依賴元件實作進行模拟并且替換掉, 進而使得單元測試将焦點隻放在目前的單元功能。

我們通過一個簡單的例子來說明mock子產品的使用。

首先,我們有腳本mock_multipy.py,主要實作的功能是Operator類中的multipy函數,在這裡我們可以假設該函數并沒有實作好,隻是存在這樣一個函數,代碼如下:

mock_multipy.py

class Operator():

def multipy(self, a, b):
    pass           

盡管我們沒有實作multipy函數,但是我們還是想對這個函數的功能進行測試,這時候我們可以借助mock子產品中的Mock類來實作。測試的腳本(mock_example.py)代碼如下:

from unittest import mock

from mock_multipy import Operator

test Operator class

class TestCount(unittest.TestCase):

def test_add(self):
    op = Operator()
    # 利用Mock類,我們假設傳回的結果為15
    op.multipy = mock.Mock(return_value=15)
    # 調用multipy函數,輸入參數為4,5,實際并未調用
    result = op.multipy(4, 5)
    # 聲明傳回結果是否為15
    self.assertEqual(result, 15)
           
unittest.main()           

讓我們對上述的代碼做一些說明。

op.multipy = mock.Mock(return_value=15)

通過Mock類來模拟調用Operator類中的multipy()函數,return_value 定義了multipy()方法的傳回值。

result = op.multipy(4, 5)

result值調用multipy()函數,輸入參數為4,5,但實際并未調用,最後通過assertEqual()方法斷言,傳回的結果是否是預期的結果為15。輸出的結果如下:

Ran 1 test in 0.002s

通過Mock類,我們即使在multipy函數并未實作的情況下,仍然能夠通過想象函數執行的結果來進行測試,這樣如果有後續的函數依賴multipy函數,也并不影響後續代碼的測試。

利用Mock子產品中的patch函數,我們可以将上述測試的腳本代碼簡化如下:

from unittest.mock import patch

@patch("mock_multipy.Operator.multipy")
def test_case1(self, tmp):
    tmp.return_value = 15
    result = Operator().multipy(4, 5)
    self.assertEqual(15, result)
           
unittest.main()           

patch()裝飾器可以很容易地模拟類或對象在子產品測試。在測試過程中,您指定的對象将被替換為一個模拟(或其他對象),并在測試結束時還原。

那如果我們後面又實作了multipy函數,是否仍然能夠測試呢?

修改mock_multipy.py腳本,代碼如下:

def multipy(self, a, b):
    return a * b           

這時候,我們再運作mock_example.py腳本,測試仍然通過,這是因為multipy函數傳回的結果仍然是我們mock後傳回的值,而并未調用真正的Operator類中的multipy函數。

我們修改mock_example.py腳本如下:

def test_add(self):
    op = Operator()
    # 利用Mock類,添加side_effect參數
    op.multipy = mock.Mock(return_value=15, side_effect=op.multipy)
    # 調用multipy函數,輸入參數為4,5,實際已調用
    result = op.multipy(4, 5)
    # 聲明傳回結果是否為15
    self.assertEqual(result, 15)
           
unittest.main()           

side_effect參數和return_value參數是相反的。它給mock配置設定了可替換的結果,覆寫了return_value。簡單的說,一個模拟工廠調用将傳回side_effect值,而不是return_value。是以,設定side_effect參數為Operator類中的multipy函數,那麼return_value的作用失效。

運作修改後的測試腳本,測試結果如下:

Ran 1 test in 0.004s

FAILED (failures=1)

15 != 20

Expected :20

Actual :15

可以發現,multipy函數傳回的值為20,不等于我們期望的值15,這是side_effect函數的作用結果使然,傳回的結果調用了Operator類中的multipy函數,是以傳回值為20。

在self.assertEqual(result, 15)中将15改成20,運作測試結果如下:

本次分享到此結束,感謝大家的閱讀~

原文位址

https://www.cnblogs.com/jclian91/p/12784994.html