軟體測試基礎 (一): 單元測試

BugBear發表於2021-01-10

轉載於公眾號 BugBear軟體測試,歡迎關注

       Hello!大家好,我是BugBear,一個專注於分享軟體測試乾貨的測試開發。
       對於軟體測試,我們先按照開發階段來進行劃分,將軟體測試分為單元測試、整合測試、系統測試、驗收測試,下面我們來聊聊單元測試。

1、什麼是單元測試?

       在正式闡述什麼是單元測試之前,我先給大家分享一個工廠組裝手機的例子。

       手機組裝流水線按照圖紙將各個電子元件組裝焊接為各個模組元件(如喇叭,聽筒,麥克,FPC,按鍵板,攝像頭,LCD等),再將各個模組元件組裝成一部完整的手機。
       如果一起順利,在給手機安裝系統後就可以正常使用了。但是很不幸,大多數情況下的手機是無法使用的,那麼就需要將已經組裝好的手機重新拆機,逐個模組排查問題,在每個模組排查中需要對每個電子元件進行檢測,通過花費大量的時間和精力才能定位到問題原因。
       那麼在後續的生產中,如何才能避免這種問題的發生呢?
       你可能立即就會想到,為什麼不在組裝焊接前,就先測試每個要用到的電子元器件呢?這樣你就可以先排除有問題的元器件,最大程度地防止組裝完成後逐級排查問題的事情發生。
       實踐也證明,這的確是一個行之有效的好辦法。
       如果把手機的生產、測試和軟體的開發、測試進行類比,你可以發現:

  • 電子元器件就像是軟體中的單元,通常是函式或者類,對單個元器件的測試就像是軟體測試中的單元測試
  • 組裝完成的功能模組元件如喇叭,聽筒,麥克,FPC,按鍵板,攝像頭,LCD等就像是軟體中的模組,對功能模組元件的測試就像是軟體中的整合測試
  • 手機全部組裝並安裝系統就像是軟體完成了預釋出版本,手機全部組裝並安裝系統完成後的開機測試就像是軟體中的系統測試

通過這個類比,相信你已經體會到了單元測試對於軟體整體質量的重要性,那麼單元測試到底是什麼呢?

單元測試是指,對軟體中的最小可測試單元在與程式其他部分相隔離的情況下進行檢查和驗證的工作,這裡的最小可測試單元通常是指函式或者類。

2、什麼是好的單元測試?


       好的單元測試應當包含四種特性:正確清晰完整健壯

  • 正確:單元測試是最基礎的要求,必須要保證所寫的函式或者類實現的功能是正確的,如果實現的功能都不能滿足,那就是缺陷!
  • 清晰:單元測試可以幫助其他開發理解函式或者類的實現,所以要求單元測試用例簡潔、清晰,需要有良好的可讀性
  • 完整:單元測試需要考慮輸入與輸出組合的各種場景,保證單元測試的覆蓋率
  • 健壯:健壯性是最容易被忽略的一項,當被測試的類或者函式被修改內部實現或者新增功能時,⼀個好的單測應該完全不需要被修改或者只有極少的修改。⽐如⼀個排序函式的單測實現是完全穩定的,它不應該跟著不同的排序演算法⽽變化

3、怎麼寫單元測試?

       可能大多數的測試人員不會接觸到單元測試的編寫,因為按照我個人的看法,開發人員根據自己寫的程式碼編寫單測用例是最合適不過的,也是最高效的。
       雖然我們不需要實際去編寫單測用例,但是我們還是需要了解怎麼寫單元測試。

       單元測試的程式碼結構一般包含三部分:分別是準備呼叫斷言

  • 準備:準備部分的⽬的是準備好調⽤所需要的外部環境,如資料,Stub(樁程式碼),Mock,臨時變數,調⽤請求,環境背景變數等等。
  • 呼叫:調⽤部分則是實際調⽤需要測試⽅法,即函式或者流程本身。
  • 斷言:斷⾔部分判斷調⽤部分的返回結果是否符合預期。 每個單元測試都應該能清晰地分出這三部分,當然有時調⽤斷⾔兩部分合在⼀起也是⽐較常見的。

4、玩轉單元測試


       下面我們來聊聊單元測試編寫用例的相關知識,首先我們需要了解單元測試的三個重要部分,即驅動程式樁程式Mock
驅動程式:驅動程式(Driver)也稱作驅動模組,用以模擬被測模組的上級模組,能夠呼叫被測模組。在測試過程中,驅動模組接收測試資料,呼叫被測模組並把相關的資料傳送給被測模組。

簡單說就是你負責測試的模組沒有main()方法入口,所以需要寫一個帶main的方法來呼叫你的模組或方法。這個就是驅動測試

樁程式:樁程式(Stub),也稱樁模組,用以模擬被測模組工作過程中所呼叫的下層模組,即被測模組本身呼叫的其他關聯函式。樁模組由被測模組呼叫,它們一般只進行很少的資料處理。

樁是指用來代替關聯程式碼或者未實現的程式碼,為了讓測試物件可以正常的執行,其實一般會硬編碼一些輸入和輸出,保證被測模組能夠正常執行

Mock:Mock除了保證Stub的功能之外,還可深入的模擬物件之間的互動方式,如:呼叫了幾次、在某種情況下是否會丟擲異常以及提供資料斷言

接下來我們通過一個例項來學習單元測試用例的編寫

# 待測試的方法
def calculator(type):
# 呼叫樁程式碼獲取資料
num1 = __stub1()
num2 = __stub2()
# 呼叫mock
mock_data = __mock_check()

# +
if type.lower() == 'add':
type = 'add'
ret = num1+num2
assert ret == mock_data[type]
print('{} + {} = {}'.format(num1,num2,ret))
return ret
# -
if type.lower() == 'minus':
type = 'minus'
ret = num1-num2
assert ret == mock_data[type]
print('{} - {} = {}'.format(num1,num2,ret))
return ret
# *
if type.lower() == 'multiply':
type = 'multiply'
ret = num1*num2
assert ret == mock_data[type]
print('{} * {} = {}'.format(num1,num2,ret))
return ret
# /
if type.lower() == 'divide':
type = 'divide'
if num2 == 0:
print('除法分母不能為0')
return '除法分母不能為0'
else:
ret = num1/num2
assert ret == mock_data[type]
print('{} / {} = {}'.format(num1,num2,ret))
return ret

# 樁程式碼1
def __stub1():
output = 20
print('my stub的值是{}'.format(output))
return output

# 樁程式碼2
def __stub2():
output = 5
print('my stub的值是{}'.format(output))
return output

# Mock程式碼 => 提供斷言資料
def __mock_check():
mock_result = {}
mock_result['add'] = 25
mock_result['minus'] = 15
mock_result['multiply'] = 100
mock_result['divide'] = 4
return mock_result

# 驅動程式
if __name__=="__main__":
print(calculator('add'))
print(calculator('minus'))
print(calculator('multiply'))
print(calculator('divide'))

上面提供的是一個簡單的單元測試,包含了驅動程式、被測物件、樁程式以及Mock程式碼

  • 驅動程式main作為被測物件的上級模組,執行時呼叫被測函式calculator
  • 被測函式calculator被呼叫後,通過樁程式碼__stub1__stub2提供測試資料
  • 被測函式calculator通過不同的入參匹配不同的場景,不同場景獲取的結果與Mock函式__mock_check進行比對斷言,校驗結果是否符合預期

被測函式成功返回如下:

my stub的值是20
my stub的值是5
20 + 5 = 25
25

被測函式失敗返回如下:

my stub的值是20
Traceback (most recent call last):
my stub的值是5
File "/Users/luozelin/Desktop/demo/unittest_demo/ut_demo.py", line 73, in <module>
print(calculator('add'))
File "/Users/luozelin/Desktop/demo/unittest_demo/ut_demo.py", line 21, in calculator
assert ret == mock_data[type]
AssertionError

文末結語

BugBear,一個專注於分享軟體測試技術乾貨的測試開發,歡迎關注公眾號:BugBear軟體測試

相關文章