淺談如何測試Python代碼
一、介紹
編寫測試檢驗(yàn)應(yīng)用程序所有不同的功能。每一個(gè)測試集中在一個(gè)關(guān)注點(diǎn)上驗(yàn)證結(jié)果是不是期望的。定期執(zhí)行測試確保應(yīng)用程序按預(yù)期的工作。當(dāng)測試覆蓋很大的時(shí)候,通過運(yùn)行測試你就有自信確保修改點(diǎn)和新增點(diǎn)不會(huì)影響應(yīng)用程序。
知識(shí)點(diǎn)
- 單元測試概念
- 使用 unittest 模塊
- 測試用例的編寫
- 異常測試
- 測試覆蓋率概念
- 使用 coverage 模塊
二、測試范圍
如果可能的話,代碼庫中的所有代碼都要測試。但這取決于開發(fā)者,如果寫一個(gè)健壯性測試是不切實(shí)際的,你可以跳過它。就像 _Nick Coghlan_(Python 核心開發(fā)成員) 在訪談里面說的:有一個(gè)堅(jiān)實(shí)可靠的測試套件,你可以做出大的改動(dòng),并確信外部可見行為保持不變。
三、單元測試
這里引用維基百科的介紹:
在計(jì)算機(jī)編程中,單元測試(英語:Unit Testing)又稱為模塊測試, 是針對(duì)程序模塊(軟件設(shè)計(jì)的最小單位)來進(jìn)行正確性檢驗(yàn)的測試工作。程序單元是應(yīng)用的最小可測試部件。在過程化編程中,一個(gè)單元就是單個(gè)程序、函數(shù)、過程等;對(duì)于面向?qū)ο缶幊?,最小單元就是方法,包括基類(超類)、抽象類、或者派生類(子類)中的方法?/p>
單元測試模塊
在 Python 里我們有 unittest 這個(gè)模塊來幫助我們進(jìn)行單元測試。
階乘計(jì)算程序
在這個(gè)例子中我們將寫一個(gè)計(jì)算階乘的程序 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]))
運(yùn)行程序:
$ python3 factorial.py 5
四、第一個(gè)測試用例
測試哪個(gè)函數(shù)?
正如你所看到的, fact(n)
這個(gè)函數(shù)執(zhí)行所有的計(jì)算,所以我們至少應(yīng)該測試這個(gè)函數(shù)。
編輯 factorial_test.py
文件,代碼如下:
import unittest from factorial import fact class TestFactorial(unittest.TestCase): """ 我們的基本測試類 """ def test_fact(self): """ 實(shí)際測試 任何以 `test_` 開頭的方法都被視作測試用例 """ res = fact(5) self.assertEqual(res, 120) if __name__ == '__main__': unittest.main()
運(yùn)行測試:
$ python3 factorial_test.py . ---------------------------------------------------------------------- Ran 1 test in 0.000s OK
說明
我們首先導(dǎo)入了 unittest 模塊,然后測試我們需要測試的函數(shù)。
測試用例是通過子類化 unittest.TestCase
創(chuàng)建的。
現(xiàn)在我們打開測試文件并且把 120 更改為 121,然后看看會(huì)發(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): """ 實(shí)際測試 任何以 `test_` 開頭的方法都被視作測試用例 """ res = fact(5) self.assertEqual(res, 120) def test_error(self): """ 測試由運(yùn)行時(shí)錯(cuò)誤引發(fā)的異常 """ self.assertRaises(ZeroDivisionError, div, 0) if __name__ == '__main__': unittest.main()
六、mounttab.py
mounttab.py 中只有一個(gè) 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)了上面的代碼并且有一個(gè)我們能容易的測試的新函數(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): """ 實(shí)際測試 任何以 `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()
運(yùn)行程序
$ python3 mounttest.py
..
----------------------------------------------------------------------
Ran 2 tests in 0.001s
OK
七、測試覆蓋率
測試覆蓋率是找到代碼庫未經(jīng)測試的部分的簡單方法。它并不會(huì)告訴你的測試好不好。
在 Python 中我們已經(jīng)有了一個(gè)不錯(cuò)的覆蓋率工具來幫助我們。你可以在實(shí)驗(yàn)樓環(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é)
知識(shí)點(diǎn)回顧:
- 單元測試概念
- 使用 unittest 模塊
- 測試用例的編寫
- 異常測試
- 測試覆蓋率概念
- 使用 coverage 模塊
本節(jié)了解了什么是單元測試,unittest 模塊怎么用,測試用例怎么寫。以及最后我們使用第三方模塊 coverage 進(jìn)行了覆蓋率測試。
在實(shí)際生產(chǎn)環(huán)境中,測試環(huán)節(jié)是非常重要的的一環(huán),即便志不在測試工程師,但以后的趨勢就是 DevOps,所以掌握良好的測試技能也是很有用的。
到此這篇關(guān)于淺談如何測試Python代碼的文章就介紹到這了,更多相關(guān)測試Python代碼內(nèi)容請(qǐng)搜索本站以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持本站!
版權(quán)聲明:本站文章來源標(biāo)注為YINGSOO的內(nèi)容版權(quán)均為本站所有,歡迎引用、轉(zhuǎn)載,請(qǐng)保持原文完整并注明來源及原文鏈接。禁止復(fù)制或仿造本網(wǎng)站,禁止在非www.sddonglingsh.com所屬的服務(wù)器上建立鏡像,否則將依法追究法律責(zé)任。本站部分內(nèi)容來源于網(wǎng)友推薦、互聯(lián)網(wǎng)收集整理而來,僅供學(xué)習(xí)參考,不代表本站立場,如有內(nèi)容涉嫌侵權(quán),請(qǐng)聯(lián)系alex-e#qq.com處理。