不拼花哨,只拼實用:Unittest指南,干貨為王!
Python為開發(fā)者提供了內(nèi)置的單元測試框架 unittest,它是一種強大的工具,能夠有效地編寫和執(zhí)行單元測試。unittest 提供了完整的測試結(jié)構(gòu),支持自動化測試的執(zhí)行,能夠?qū)y試用例進行組織,并且提供了豐富的斷言方法。最終,unittest 會生成詳細(xì)的測試報告,這個框架非常簡單且易于使用。
unittest核心概念
在 unittest 中,有幾個核心概念:
- TestCase(測試用例):每個測試用例實例用于封裝一個或多個測試函數(shù)。
- TestSuite(測試套件):這是多個測試用例的集合,用于組織和執(zhí)行多個測試用例。
- TestLoader(測試加載器):這是一個用于將測試用例加載到測試套件中的工具。
- TextTestRunner(測試運行器):這是用于執(zhí)行測試用例的運行器,負(fù)責(zé)運行測試并生成結(jié)果報告。
- Fixture(環(huán)境管理機制):這是測試用例的環(huán)境搭建和銷毀部分,包括前置條件和后置條件。
unittest的工作流程
- 編寫繼承自 unittest.TestCase 的測試用例類,其中每個測試函數(shù)都是一個獨立的測試用例。
- 使用 TestLoader 加載測試用例,并將它們組織成 TestSuite 對象。
- 使用 TestRunner 運行 TestSuite 中的測試用例,并輸出測試結(jié)果。
使用unittest初級指南
- 導(dǎo)入 unittest 模塊以及被測試的文件或類。
- 創(chuàng)建一個測試類,并繼承 unittest.TestCase,所有自定義的單元測試類都要繼承它,作為基類。
- 重寫 setUp 和 tearDown 方法,用于初始化和清理測試環(huán)境(如果有必要)。
- 定義測試函數(shù),函數(shù)名以 test_ 開頭,這樣才能被識別并執(zhí)行。
- 在測試函數(shù)中使用斷言來判斷測試結(jié)果是否符合預(yù)期。
- 調(diào)用 unittest.main() 方法運行測試用例,按照函數(shù)名的排序執(zhí)行測試。
以下是一個簡單的例子:
import unittest
def login(username, password):
if username == 'kira' and password == '123':
res = {"code": 200, "msg": "登錄成功"}
return res
return {"code": 400, "msg": "登錄失敗"}
class TestLogin(unittest.TestCase):
def test_login_success(self):
"""測試登錄成功"""
test_data = {"username": "kira", "password": "test"}
expect_data = {"code": 200, "msg": "登錄成功"}
res = login(**test_data)
self.assertEqual(res, expect_data)
def test_login_error_with_error_password(self):
"""賬號正確,密碼錯誤,登錄失敗"""
test_data = {"username": "kira", "password": "12345"}
expect_data = {"code": 400, "msg": "登錄失敗"}
res = login(**test_data)
self.assertEqual(res, expect_data)
# 更多測試函數(shù)類似...
if __name__ == '__main__':
unittest.main()
以上是一個簡單的測試用例,包含了兩個測試函數(shù)。運行腳本將輸出測試結(jié)果。
unittest核心概念
測試腳手架
測試腳手架 是測試用例的前置條件和后置條件,確保測試環(huán)境的初始化和清理,從而保證測試的準(zhǔn)確性和可靠性。
import unittest
class MyTestCase(unittest.TestCase):
@classmethod
def setUpClass(cls):
# 類級別的前置條件設(shè)置,整個類運行最先只執(zhí)行一次
print("setUpClass")
@classmethod
def tearDownClass(cls):
# 類級別的后置條件清理,整個類運行最后結(jié)束執(zhí)行一次
print("tearDownClass")
def setUp(self):
# 測試方法級別的前置條件設(shè)置,所有測試方法運行前都執(zhí)行一次
print("setUp")
def tearDown(self):
# 測試方法級別的后置條件清理,所有測試方法運行結(jié)束都執(zhí)行一次
print("tearDown")
def test_example(self):
# 測試用例
print("test_example")
if __name__ == "__main__":
unittest.main()
- setUp():每個測試方法運行前執(zhí)行,用于測試前置的初始化工作。
- tearDown():每個測試方法結(jié)束后執(zhí)行,用于測試后的清理工作。
- setUpClass():所有的測試方法運行前執(zhí)行,用于單元測試類運行前的準(zhǔn)備工作。使用 @classmethod 裝飾器裝飾,整個測試類運行過程中只會執(zhí)行一次。
- tearDownClass():所有的測試方法結(jié)束后執(zhí)行,用于單元測試類運行后的清理工作。使用 @classmethod 裝飾器裝飾,整個測試類運行過程中只會執(zhí)行一次。
測試用例
測試用例 是最小的測試單元,用于檢測特定的輸入集合的特定的返回值。unittest 提供了 TestCase 基類,所有的測試類都需要繼承該基類,而在該類下的函數(shù)如果以 test_ 開頭,則被標(biāo)識為測試函數(shù):
class MyTestCase(unittest.TestCase
):
def test_addition(self):
result = 2 + 3
self.assertEqual(result, 5) # 使用斷言方法驗證結(jié)果是否相等
def test_subtraction(self):
result = 5 - 3
self.assertTrue(result == 2) # 使用斷言方法驗證結(jié)果是否為True
# 更多測試用例函數(shù)...
斷言方法
以下是常用的斷言方法:
- assertEqual(a, b, msg=None):驗證 a 等于 b。
- assertNotEqual(a, b):驗證 a 不等于 b。
- assertTrue(x):驗證 x 是否為 True。
- assertFalse(x):驗證 x 是否為 False。
- assertIs(a, b):驗證 a 是否是 b。
- assertIsNot(a, b):驗證 a 是否不是 b。
- assertIsNone(x):驗證 x 是否為 None。
- assertIsNotNone(x):驗證 x 是否不為 None。
- assertIn(a, b):驗證 a 是否在 b 中。
- assertNotIn(a, b):驗證 a 是否不在 b 中。
- assertIsInstance(a, b):驗證 a 是否是 b 類型的實例。
- assertNotIsInstance(a, b):驗證 a 是否不是 b 類型的實例。
可以使用這些方法進行斷言,也可以直接使用原生的assert來斷言,如果斷言失敗,測試用例會被定義為執(zhí)行失敗。
忽略特定測試方法
unittest 提供了一些方法來跳過特定的測試用例:
- @unittest.skip(reason):強制跳過,reason 是跳過的原因。
- @unittest.skipIf(condition, reason):當(dāng) condition 為 True 時跳過。
- @unittest.skipUnless(condition, reason):當(dāng) condition 為 False 時跳過。
- @unittest.expectedFailure:如果測試失敗,這個測試用例不會計入失敗的統(tǒng)計。
使用實例方法:self.skipTest() 使用和上述類似。
import sys
import unittest
class Test1(unittest.TestCase):
@unittest.expectedFailure # 即使失敗也會被計為成功的用例
def test_1(self):
assert 1 + 1 == 3
@unittest.skip('無條件跳過') # 不管什么情況都會進行跳過
def test_2(self):
print("2+2...", 4)
@unittest.skipIf(sys.platform == "win32", "跳過") # 如果系統(tǒng)平臺為 Windows 則跳過
def test_3(self):
print("3+3...", 6)
@unittest.skipUnless(sys.platform == "win32", "跳過") # 除非系統(tǒng)平臺為 Windows,否則跳過
def test_4(self):
print("4+4...", 8)
def test_5(self):
self.skipTest("跳過")
print("5+5...", 10)
if __name__ == "__main__":
unittest.main(verbosity=2)
測試套件
測試套件用于收集和組織多個測試用例,便于集中執(zhí)行。
- 通過 unittest.main() 方法直接加載單元測試的測試模塊,這是一種簡單的加載方式。所有測試用例的執(zhí)行順序按照方法名的字符串表示的 ASCII 碼升序排序,通過命名時使用 test_01_xxx 來指定執(zhí)行順序。
- 將所有的單元測試用例 TestCase 加載到測試套件 Test Suite 集合中,然后一次性加載所有測試對象。
通過 TestSuite 對象收集
此方式適用于需要自定義組合特定測試用例的情況。
import unittest
class MyTestCase(unittest.TestCase):
def test_addition(self):
result = 2 + 3
self.assertEqual(result, 5)
def suite():
suite = unittest.TestSuite()
suite.addTest(MyTestCase('test_addition'))
return suite
if __name__ == '__main__':
runner = unittest.TextTestRunner()
runner.run(suite())
通過 TestLoader 對象收集
TestLoader 是 unittest 框架提供的加載測試用例的類。
import unittest
if __name__ == '__main__':
loader = unittest.defaultTestLoader
# 自動加載當(dāng)前模塊中所有以 'test_' 開頭的測試用例函數(shù)
suite = loader.loadTestsFromModule(__name__)
runner = unittest.TextTestRunner()
runner.run(suite)
import unittest
class MyTestCase(unittest.TestCase):
def test_addition(self):
result = 2 + 3
self.assertEqual(result, 5)
if __name__ == '__main__':
loader = unittest.defaultTestLoader
# 自動加載 MyTestCase 類中的所有測試用例
suite = loader.loadTestsFromTestCase(MyTestCase)
runner = unittest.TextTestRunner()
runner.run(suite)
import unittest
if __name__ == '__main__':
loader = unittest.defaultTestLoader
# 自動加載指定名稱的測試用例
suite = loader.loadTestsFromName('module.MyTestCase.test_addition')
runner = unittest.TextTestRunner()
runner.run(suite)
import unittest
if __name__ == '__main__':
loader = unittest.defaultTestLoader
# 自動發(fā)現(xiàn)并加載指定目錄中的測試用例模塊
suite = loader.discover(start_dir='test_directory', pattern='test_*.py', top_level_dir=None)
runner = unittest.TextTestRunner()
runner.run(suite)
測試運行器
測試運行器是用于執(zhí)行和輸出測試結(jié)果的組件。常用的運行器有:
- unittest.TextTestRunner:這是 unittest 框架中默認(rèn)的測試運行器,會在命令行輸出測試結(jié)果。通過調(diào)用 run() 方法運行測試套件,并將測試結(jié)果打印到控制臺。
import unittest
if __name__ == '__main__':
loader = unittest.defaultTestLoader
suite = loader.discover(start_dir='tests', pattern='test_*.py')
runner = unittest.TextTestRunner()
result = runner.run(suite)
- HTMLTestRunner:這是一個第三方庫,能夠生成漂亮的 HTML 測試報告,需要進行安裝。你可以通過搜索獲取相關(guān)文件進行安裝。
import unittest
from HTMLTestRunner import HTMLTestRunner
if __name__ == '__main__':
loader = unittest.defaultTestLoader
suite = loader.discover(start_dir='tests', pattern='test_*.py')
with open('test_report.html', 'wb') as report_file:
runner = HTMLTestRunner(stream=report_file, title='Test Report', description='Test Results')
result = runner.run(suite)
- XMLTestRunner:這是另一個第三方庫,用于生成 XML 格式的測試報告。
import unittest
from xmlrunner import XMLTestRunner
if __name__ == '__main__':
loader = unittest.defaultTestLoader
suite = loader.discover(start_dir='tests', pattern='test_*.py')
with open('test_report.xml', 'wb') as report_file:
runner = XMLTestRunner(output=report_file)
result = runner.run(suite)
你也可以自定義測試運行器。繼承 unittest.TestRunner 類并實現(xiàn) run() 方法,以創(chuàng)建自己的測試運行器。
import unittest
class MyTestRunner(unittest.TextTestRunner):
def run(self, test):
print("Running tests with MyTestRunner")
result = super().run(test)
return result
if __name__ == '__main__':
loader = unittest.defaultTestLoader
suite = loader.discover(start_dir='tests', pattern='test_*.py')
runner = MyTestRunner()
result = runner.run(suite)
通常使用 HTMLTestRunner 即可滿足需求,它非常易用。
實戰(zhàn)一個測試案例
假設(shè)有一個測試函數(shù) login:
# login.py
def login(username, password):
"""模擬登錄校驗"""
if username == 'kira' and password == '123456':
return {"code": 0, "msg": "登錄成功"}
else:
return {"code": 1, "msg": "賬號或密碼不正確"}
設(shè)計用例
根據(jù)函數(shù)的參數(shù)和邏輯,設(shè)計如下用例:
編寫測試用例并運行
import unittest
from login import login
class TestLogin(unittest.TestCase):
def test_login_correct(self):
"""測試賬號密碼正確"""
test_data = {"username": "kira", "password": "123456"}
expect_data = {"code": 0, "msg": "登錄成功"}
res = login(**test_data)
self.assertEqual(res, expect_data)
def test_login_wrong_password(self):
"""測試賬號正確密碼不正確"""
test_data = {"username": "kira", "password": "123"}
expect_data = {"code": 1, "msg": "賬號或密碼不正確"}
res = login(**test_data)
self.assertEqual(res, expect_data)
def test_login_wrong_username(self):
"""測試賬號錯誤密碼正確"""
test_data = {"username": "kir", "password": "123456"}
expect_data = {"code": 1, "msg": "賬號或密碼不正確"}
res = login(**test_data)
self.assertEqual(res, expect_data)
if __name__ == '__main__':
unittest.main()
這是一個簡單的測試用例,包含了三個測試函數(shù)。運行測試用例后,會輸出測試結(jié)果,看完是否覺得unittest非常簡單易用。ner.run(suite)