單元測試
- unittest包括Python標準庫中的測試模型。創建測試用例通過繼承unittest.Testcase來實現。
#簡單測試字符串的方法
import unittest
class TestStringMethods(unittest.TestCase):
def test_upper(self):
self.assertEqual('foo'.upper(), 'FOO')
def test_isupper(self):
self.assertTrue('FOO'.isupper())
self.assertFalse('Foo'.isupper())
def test_split(self):
s = 'hello world'
self.assertEqual(s.split(), ['hello', 'world'])
# check that s.split fails when the separator is not a string
with self.assertRaises(TypeError):
s.split(2)
if __name__ == '__main__':
unittest.main()
- 一個類繼承了unittest.TestCase就是創建了一個測試樣例。每一個測試的關鍵是調用assertEqual()來檢查預期的輸出,調用assertTrue()或者assertFalse()來驗證一個條件。調用assertRaises()來驗證拋出一個特定的異常。使用這些方法而不使用assert語句是爲了讓測試運行者能聚合所有測試結果併產生結果報告。
- 測試用例結構。在運行每一個測試用例的時候,setUp(),tearDown(),init()會被調用一次。如果同時存在多個前置操作相同的測試,我們可以把測試的前置操作從測試代碼中拆解出來,並實現測試前置方法setUp()。在運行測試用例的時候,測試框架會自動地爲每個單獨測試調用前置方法。
import unittest
class WidgetTestCase(unittest.TestCase):
def setUp(self):
self.widget = Widget('The widget')
def test_default_widget_size(self):
self.assertEqual(self.widget.size(), (50,50),
'incorrect default size')
def test_widget_resize(self):
self.widget.resize(100,150)
self.assertEqual(self.widget.size(), (100,150),
'wrong size after resize')
類似的,tearDown()方法在測試方法啊運行後進行清理工作。無論測試方法是否成功都會運行tearDown()。
import unittest
class WidgetTestCase(unittest.TestCase):
def setUp(self):
self.widget = Widget('The widget')
def tearDown(self):
self.widget.dispose()
- unittest.main()提供了一個測試腳本的命令行接口。unittest 模塊可以通過命令行運行模塊、類和獨立測試方法的測試
python -m unittest test_module1 test_module2
python -m unittest test_module.TestClass
python -m unittest test_module.TestClass.test_method
- unittest提供FunctionTestCase類,可用於打包已有的測試函數,並支持設置前置函數和後置函數。
#假設有一個測試函數
def testSomething():
something = makeSomething()
assert something.name is not None
#可以創建等價的測試用例,並且前置函數和後置函數
testcase = unittest.FunctionTestCase(testSomething,
setUp=makeSomethingDB,
tearDown=deleteSomethingDB)
- 3.1版本支持跳過測試和預計測試用例失敗
- @unittest.skip(reason) 跳過被這個裝飾器修飾的測試用例,reason表示原因(字符串)
- @unittest.skipIf(condition, reason) 當condition爲True的時候,跳過被這個裝飾器修飾的測試用例
- @unittest.skipUnless(condition, reason) 當condition爲False的時候,跳過被這個裝飾器修飾的測試用例,也就是condition成立的時候執行這個測試用例
- @unittest.expectedFailure 把測試用例標記爲預計失敗,如果測試不通過,表示測試用例成功,測試通過,表示測試用例失敗
#跳過的舉例
class MyTestCase(unittest.TestCase):
@unittest.skip("demonstrating skipping")
def test_nothing(self):
self.fail("shouldn't happen")
@unittest.skipIf(mylib.__version__ < (1, 3),"not supported in this library version")
def test_format(self):
# Tests that work for only a certain version of the library.
pass
@unittest.skipUnless(sys.platform.startswith("win"), "requires Windows")
def test_windows_support(self):
# windows specific testing code
pass
def test_maybe_skipped(self):
if not external_resource_available():
self.skipTest("external resource not available")
# test code that depends on the external resource
pass
#預計失敗的舉例
class ExpectedFailureTestCase(unittest.TestCase):
@unittest.expectedFailure
def test_fail(self):
self.assertEqual(1, 0, "broken")
- 3.4版本的新功能,通過subTest()來展示下標
class NumbersTest(unittest.TestCase):
def test_even(self):
"""
Test that numbers between 0 and 5 are all even.
"""
for i in range(0, 6):
with self.subTest(i=i):
self.assertEqual(i % 2, 0)
那麼在展示結果的時候就是
======================================================================
FAIL: test_even (__main__.NumbersTest) (i=1)
----------------------------------------------------------------------
Traceback (most recent call last):
File "subtests.py", line 32, in test_even
self.assertEqual(i % 2, 0)
AssertionError: 1 != 0
======================================================================
FAIL: test_even (__main__.NumbersTest) (i=3)
----------------------------------------------------------------------
Traceback (most recent call last):
File "subtests.py", line 32, in test_even
self.assertEqual(i % 2, 0)
AssertionError: 1 != 0
======================================================================
FAIL: test_even (__main__.NumbersTest) (i=5)
----------------------------------------------------------------------
Traceback (most recent call last):
File "subtests.py", line 32, in test_even
self.assertEqual(i % 2, 0)
AssertionError: 1 != 0
可以通過i的展示來尋找問題。
- testCase提供一些斷言的方法來檢查並報告故障
方法 | 檢查 | 版本 |
---|---|---|
assertEqual(a,b,Msg=None) | 檢查a,b是否相等 | |
assertNotEqual(a,b,Msg=None) | 檢查a.b.是否不相等 | |
asserTrue(x) | 檢查x是否爲真 | |
asserFlase(x) | 檢查是否爲假 | |
assertIs(a,b) | 檢查a.b是否是同類型 | 3.1 |
assertIsNot(a,b) | 檢查a.b是否不是同類型的 | 3.1 |
asserIsNone(x) | 檢查x是否爲None | 3.1 |
assertIsNotNone(x) | 檢查x是否不是None | 3.1 |
assertIn(a,b) | 檢查a是否在b中 | 3.1 |
assertNotIn(a,b) | 檢查a是否不在b中的值 | 3.1 |
assertIsInstance(object,classinfo) | 檢查如果object是classinfo的實例或者是其子類,則返回True | 3.2 |
assertNotIsInstance(object,classinfo) | 檢查如果object不是classinfo的實例或者是其子類,則返回True | 3.2 |
文檔測試
- python中有doctest模塊查找零碎文本,就像在Python中docstrings內的交互式會話,執行那些會話以證實工作正常。
#簡單的函數文檔測試
def square(x):
"""返回 x 的平方。
>>> square(2)
4
>>> square(-2)
4
"""
return x + x
if __name__ == '__main__':
import doctest
doctest.testmod()
然後運行之後的命令行展示
**********************************************************************
File "test_doctest.py", line 7, in __main__.square
Failed example:
square(-2)
Expected:
4
Got:
-4
**********************************************************************
工具 py.test
#舉例有一個測試文件
# content of test_sample.py
def func(x):
return x + 1
def test_answer():
assert func(3) == 5
在這個文件目錄下運行
C:\Users\Administrator\Desktop\test>py.test
================================================= test session starts =================================================
platform win32 -- Python 3.7.6, pytest-5.3.5, py-1.8.1, pluggy-0.13.1
rootdir: C:\Users\Administrator\Desktop\test
collected 1 item
test_doctest.py F [100%]
====================================================== FAILURES =======================================================
_____________________________________________________ test_answer _____________________________________________________
def test_answer():
> assert func(3) == 5
E assert 4 == 5
E + where 4 = func(3)
test_doctest.py:6: AssertionError
================================================== 1 failed in 0.03s ==================================================