淺談如何測試Python代碼
一、介紹
編寫測試檢驗應(yīng)用程序所有不同的功能。每一個測試集中在一個關(guān)注點上驗證結(jié)果是不是期望的。定期執(zhí)行測試確保應(yīng)用程序按預(yù)期的工作。當(dāng)測試覆蓋很大的時候,通過運行測試你就有自信確保修改點和新增點不會影響應(yīng)用程序。
知識點
- 單元測試概念
- 使用 unittest 模塊
- 測試用例的編寫
- 異常測試
- 測試覆蓋率概念
- 使用 coverage 模塊
二、測試范圍
如果可能的話,代碼庫中的所有代碼都要測試。但這取決于開發(fā)者,如果寫一個健壯性測試是不切實際的,你可以跳過它。就像 _Nick Coghlan_(Python 核心開發(fā)成員) 在訪談里面說的:有一個堅實可靠的測試套件,你可以做出大的改動,并確信外部可見行為保持不變。
三、單元測試
這里引用維基百科的介紹:
在計算機編程中,單元測試(英語:Unit Testing)又稱為模塊測試, 是針對程序模塊(軟件設(shè)計的最小單位)來進行正確性檢驗的測試工作。程序單元是應(yīng)用的最小可測試部件。在過程化編程中,一個單元就是單個程序、函數(shù)、過程等;對于面向?qū)ο缶幊?,最小單元就是方法,包括基類(超類)、抽象類、或者派生類(子類)中的方法?/p>
單元測試模塊
在 Python 里我們有 unittest 這個模塊來幫助我們進行單元測試。
階乘計算程序
在這個例子中我們將寫一個計算階乘的程序 factorial.py:
import sys
def fact(n):
"""
階乘函數(shù)
:arg n: 數(shù)字
:returns: n 的階乘
"""
if n == 0:
return 1
return n * fact(n -1)
def div(n):
"""
只是做除法
"""
res = 10 / n
return res
def main(n):
res = fact(n)
print(res)
if __name__ == '__main__':
if len(sys.argv) > 1:
main(int(sys.argv[1]))
運行程序:
$ python3 factorial.py 5
四、第一個測試用例
測試哪個函數(shù)?
正如你所看到的, fact(n) 這個函數(shù)執(zhí)行所有的計算,所以我們至少應(yīng)該測試這個函數(shù)。
編輯 factorial_test.py 文件,代碼如下:
import unittest
from factorial import fact
class TestFactorial(unittest.TestCase):
"""
我們的基本測試類
"""
def test_fact(self):
"""
實際測試
任何以 `test_` 開頭的方法都被視作測試用例
"""
res = fact(5)
self.assertEqual(res, 120)
if __name__ == '__main__':
unittest.main()
運行測試:
$ python3 factorial_test.py . ---------------------------------------------------------------------- Ran 1 test in 0.000s OK
說明
我們首先導(dǎo)入了 unittest 模塊,然后測試我們需要測試的函數(shù)。
測試用例是通過子類化 unittest.TestCase 創(chuàng)建的。
現(xiàn)在我們打開測試文件并且把 120 更改為 121,然后看看會發(fā)生什么?
各類 assert 語句
| Method | Checks that | New in |
| assertEqual(a, b) | a == b |
|
| assertNotEqual(a, b) | a != b |
|
| assertTrue(x) | bool(x) is True |
|
| assertFalse(x) | bool(x) is False |
|
| assertIs(a, b) | a is b |
2.7 |
| assertIsNot(a, b) | a is not b |
2.7 |
| assertIsNone(x) | x is None |
2.7 |
| assertIsNotNone(x) | x is not None |
2.7 |
| assertIn(a, b) | a in b |
2.7 |
| assertNotIn(a, b) | a not in b |
2.7 |
| assertIsInstance(a, b) | isinstance(a, b) |
2.7 |
| assertNotIsInstance(a, b) | not isinstance(a, b) |
2.7 |
五、異常測試
如果我們在 factorial.py 中調(diào)用 div(0),我們能看到異常被拋出。
我們也能測試這些異常,就像這樣:
self.assertRaises(ZeroDivisionError, div, 0)
完整代碼:
import unittest
from factorial import fact, div
class TestFactorial(unittest.TestCase):
"""
我們的基本測試類
"""
def test_fact(self):
"""
實際測試
任何以 `test_` 開頭的方法都被視作測試用例
"""
res = fact(5)
self.assertEqual(res, 120)
def test_error(self):
"""
測試由運行時錯誤引發(fā)的異常
"""
self.assertRaises(ZeroDivisionError, div, 0)
if __name__ == '__main__':
unittest.main()
六、mounttab.py
mounttab.py 中只有一個 mount_details() 函數(shù),函數(shù)分析并打印掛載詳細(xì)信息。
import os
def mount_details():
"""
打印掛載詳細(xì)信息
"""
if os.path.exists('/proc/mounts'):
fd = open('/proc/mounts')
for line in fd:
line = line.strip()
words = line.split()
print('{} on {} type {}'.format(words[0],words[1],words[2]), end=' ')
if len(words) > 5:
print('({})'.format(' '.join(words[3:-2])))
else:
print()
fd.close()
if __name__ == '__main__':
mount_details()
重構(gòu) mounttab.py
現(xiàn)在我們在 mounttab2.py 中重構(gòu)了上面的代碼并且有一個我們能容易的測試的新函數(shù) parse_mounts()。
import os
def parse_mounts():
"""
分析 /proc/mounts 并 返回元祖的列表
"""
result = []
if os.path.exists('/proc/mounts'):
fd = open('/proc/mounts')
for line in fd:
line = line.strip()
words = line.split()
if len(words) > 5:
res = (words[0],words[1],words[2],'({})'.format(' '.join(words[3:-2])))
else:
res = (words[0],words[1],words[2])
result.append(res)
fd.close()
return result
def mount_details():
"""
打印掛載詳細(xì)信息
"""
result = parse_mounts()
for line in result:
if len(line) == 4:
print('{} on {} type {} {}'.format(*line))
else:
print('{} on {} type {}'.format(*line))
if __name__ == '__main__':
mount_details()
同樣我們測試代碼,編寫 mounttest.py 文件:
#!/usr/bin/env python
import unittest
from mounttab2 import parse_mounts
class TestMount(unittest.TestCase):
"""
我們的基本測試類
"""
def test_parsemount(self):
"""
實際測試
任何以 `test_` 開頭的方法都被視作測試用例
"""
result = parse_mounts()
self.assertIsInstance(result, list)
self.assertIsInstance(result[0], tuple)
def test_rootext4(self):
"""
測試找出根文件系統(tǒng)
"""
result = parse_mounts()
for line in result:
if line[1] == '/' and line[2] != 'rootfs':
self.assertEqual(line[2], 'ext4')
if __name__ == '__main__':
unittest.main()
運行程序
$ python3 mounttest.py
..
----------------------------------------------------------------------
Ran 2 tests in 0.001s
OK
七、測試覆蓋率
測試覆蓋率是找到代碼庫未經(jīng)測試的部分的簡單方法。它并不會告訴你的測試好不好。
在 Python 中我們已經(jīng)有了一個不錯的覆蓋率工具來幫助我們。你可以在實驗樓環(huán)境中安裝它:
$ sudo pip3 install coverage
覆蓋率示例
$ coverage3 run mounttest.py
..
----------------------------------------------------------------------
Ran 2 tests in 0.013s
OK
$ coverage3 report -m
Name Stmts Miss Cover Missing
--------------------------------------------
mounttab2.py 22 7 68% 16, 25-30, 34
mounttest.py 14 0 100%
--------------------------------------------
TOTAL 36 7 81%
我們還可以使用下面的命令以 HTML 文件的形式輸出覆蓋率結(jié)果,然后在瀏覽器中查看它。
$ coverage3 html

八、總結(jié)
知識點回顧:
- 單元測試概念
- 使用 unittest 模塊
- 測試用例的編寫
- 異常測試
- 測試覆蓋率概念
- 使用 coverage 模塊
本節(jié)了解了什么是單元測試,unittest 模塊怎么用,測試用例怎么寫。以及最后我們使用第三方模塊 coverage 進行了覆蓋率測試。
在實際生產(chǎn)環(huán)境中,測試環(huán)節(jié)是非常重要的的一環(huán),即便志不在測試工程師,但以后的趨勢就是 DevOps,所以掌握良好的測試技能也是很有用的。
到此這篇關(guān)于淺談如何測試Python代碼的文章就介紹到這了,更多相關(guān)測試Python代碼內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
一文帶你了解Python中不同數(shù)據(jù)對象的空值校驗方法
空值校驗在數(shù)據(jù)處理和應(yīng)用程序開發(fā)中是一個非常重要的任務(wù),Python提供了多種方式來檢查不同數(shù)據(jù)對象(如字符串、列表、字典、集合等)是否為空或包含空值,下面就跟隨小編一起來學(xué)習(xí)一下吧2024-01-01
Python模仿POST提交HTTP數(shù)據(jù)及使用Cookie值的方法
這篇文章主要介紹了Python模仿POST提交HTTP數(shù)據(jù)及使用Cookie值的方法,通過兩種不同的實現(xiàn)方法較為詳細(xì)的講述了HTTP數(shù)據(jù)通信及cookie的具體用法,需要的朋友可以參考下2014-11-11
Python中自定義函方法與參數(shù)具有默認(rèn)值的函數(shù)
這篇文章主要介紹了Python中自定義函方法與參數(shù)具有默認(rèn)值的函數(shù),在Python編程中,可以使用已經(jīng)定義好的函數(shù),也可以自定義函數(shù)實現(xiàn)某些特殊的功能,更多相關(guān)資料,請需要的人參考下面文章內(nèi)容2022-02-02
python3+PyQt5實現(xiàn)使用剪貼板做復(fù)制與粘帖示例
本篇文章主要介紹了python3+PyQt5實現(xiàn)使用剪貼板做復(fù)制與粘帖示例,具有一定的參考價值,有興趣的可以了解一下。2017-01-01

