不拼花哨,只拼實用:unittest指南,乾貨為王!

软件测试潇潇發表於2024-03-27

Python為開發者提供了內建的單元測試框架 unittest,它是一種強大的工具,能夠有效地編寫和執行單元測試。unittest 提供了完整的測試結構,支援自動化測試的執行,能夠對測試用例進行組織,並且提供了豐富的斷言方法。最終,unittest 會生成詳細的測試報告,這個框架非常簡單且易於使用。

unittest核心概念

unittest 中,有四個核心概念:

  1. TestCase(測試用例):每個測試用例例項用於封裝一個或多個測試函式。
  2. TestSuite(測試套件):這是多個測試用例的集合,用於組織和執行多個測試用例。
  3. TestLoader(測試載入器):這是一個用於將測試用例載入到測試套件中的工具。
  4. TextTestRunner(測試執行器):這是用於執行測試用例的執行器,負責執行測試並生成結果報告。
  5. Fixture(環境管理機制):這是測試用例的環境搭建和銷燬部分,包括前置條件和後置條件。
不拼花哨,只拼實用:unittest指南,乾貨為王!

unittest的工作流程

  1. 編寫繼承自 unittest.TestCase 的測試用例類,其中每個測試函式都是一個獨立的測試用例。
  2. 使用 TestLoader 載入測試用例,並將它們組織成 TestSuite 物件。
  3. 使用 TestRunner 執行 TestSuite 中的測試用例,並輸出測試結果。
不拼花哨,只拼實用:unittest指南,乾貨為王!

使用unittest初級指南

  1. 匯入 unittest 模組以及被測試的檔案或類。
  2. 建立一個測試類,並繼承 unittest.TestCase,所有自定義的單元測試類都要繼承它,作為基類。
  3. 重寫 setUptearDown 方法,用於初始化和清理測試環境(如果有必要)。
  4. 定義測試函式,函式名以 test_ 開頭,這樣才能被識別並執行。
  5. 在測試函式中使用斷言來判斷測試結果是否符合預期。
  6. 呼叫 unittest.main() 方法執行測試用例,按照函式名的排序執行測試。

以下是一個簡單的例子:

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)

    # 更多測試函式類似...

if __name__ == '__main__':
    unittest.main()

以上是一個簡單的測試用例,包含了兩個測試函式。執行指令碼將輸出測試結果。

unittest核心概念

測試腳手架

測試腳手架 是測試用例的前置條件和後置條件,確保測試環境的初始化和清理,從而保證測試的準確性和可靠性。

import unittest

class MyTestCase(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        # 類級別的前置條件設定,整個類執行最先只執行一次
        print("setUpClass")

    @classmethod
    def tearDownClass(cls):
        # 類級別的後置條件清理,整個類執行最後結束執行一次
        print("tearDownClass")

    def setUp(self):
        # 測試方法級別的前置條件設定,所有測試方法執行前都執行一次
        print("setUp")

    def tearDown(self):
        # 測試方法級別的後置條件清理,所有測試方法執行結束都執行一次
        print("tearDown")

    def test_example(self):
        # 測試用例
        print("test_example")

if __name__ == "__main__":
    unittest.main()
  1. setUp():每個測試方法執行前執行,用於測試前置的初始化工作。
  2. tearDown():每個測試方法結束後執行,用於測試後的清理工作。
  3. setUpClass():所有的測試方法執行前執行,用於單元測試類執行前的準備工作。使用 @classmethod 裝飾器裝飾,整個測試類執行過程中只會執行一次。
  4. tearDownClass():所有的測試方法結束後執行,用於單元測試類執行後的清理工作。使用 @classmethod 裝飾器裝飾,整個測試類執行過程中只會執行一次。

測試用例

測試用例 是最小的測試單元,用於檢測特定的輸入集合的特定的返回值。unittest 提供了 TestCase 基類,所有的測試類都需要繼承該基類,而在該類下的函式如果以 test_ 開頭,則被標識為測試函式:

class MyTestCase(unittest.TestCase

):

    def test_addition(self):
        result = 2 + 3
        self.assertEqual(result, 5)  # 使用斷言方法驗證結果是否相等

    def test_subtraction(self):
        result = 5 - 3
        self.assertTrue(result == 2)  # 使用斷言方法驗證結果是否為True

    # 更多測試用例函式...

斷言方法

以下是常用的斷言方法:

  • 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來斷言,如果斷言失敗,測試用例會被定義為執行失敗。

忽略特定測試方法

unittest 提供了一些方法來跳過特定的測試用例:

  • @unittest.skip(reason):強制跳過,reason 是跳過的原因。
  • @unittest.skipIf(condition, reason):當 condition 為 True 時跳過。
  • @unittest.skipUnless(condition, reason):當 condition 為 False 時跳過。
  • @unittest.expectedFailure:如果測試失敗,這個測試用例不會計入失敗的統計。
  • 使用例項方法: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", "跳過")  # 如果系統平臺為 Windows 則跳過
    def test_3(self):
        print("3+3...", 6)

    @unittest.skipUnless(sys.platform == "win32", "跳過")  # 除非系統平臺為 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)

測試套件

測試套件用於收集和組織多個測試用例,便於集中執行。

  1. 透過 unittest.main() 方法直接載入單元測試的測試模組,這是一種簡單的載入方式。所有測試用例的執行順序按照方法名的字串表示的 ASCII 碼升序排序,透過命名時使用 test_01_xxx 來指定執行順序。
  2. 將所有的單元測試用例 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 物件收集

TestLoaderunittest 框架提供的載入測試用例的類。

import unittest

if __name__ == '__main__':
    loader = unittest.defaultTestLoader
    
    # 自動載入當前模組中所有以 'test_' 開頭的測試用例函式
    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
    
    # 自動發現並載入指定目錄中的測試用例模組
    suite = loader.discover(start_dir='test_directory', pattern='test_*.py', top_level_dir=None)

    runner = unittest.TextTestRunner()
    runner.run(suite)

測試執行器

測試執行器是用於執行和輸出測試結果的元件。常用的執行器有:

  • unittest.TextTestRunner:這是 unittest 框架中預設的測試執行器,會在命令列輸出測試結果。透過呼叫 run() 方法執行測試套件,並將測試結果列印到控制檯。
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 測試報告,需要進行安裝。你可以透過搜尋獲取相關檔案進行安裝。
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 類並實現 run() 方法,以建立自己的測試執行器。

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 即可滿足需求,它非常易用。

實戰一個測試案例

假設有一個測試函式 login

# login.py
def login(username, password):
    """模擬登入校驗"""
    if username == 'kira' and password == '123456':
        return {"code": 0, "msg": "登入成功"}
    else:
        return {"code": 1, "msg": "賬號或密碼不正確"}

設計用例

根據函式的引數和邏輯,設計如下用例:

序號標題測試資料預期結果實際結果
1 賬號密碼正確 {"username": "kira", "password": "123456"} {"code": 0, "msg": "登入成功"}
2 賬號正確密碼不正確 {"username": "kira", "password": "123"} {"code": 1, "msg": "賬號或密碼不正確"}
3 賬號錯誤密碼正確 {"username": "kir", "password": "123456"} {"code": 1, "msg": "賬號或密碼不正確"}

編寫測試用例並執行

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()

這是一個簡單的測試用例,包含了三個測試函式。執行測試用例後,會輸出測試結果,看完是否覺得unittest非常簡單易用。ner.run(suite)

最後感謝每一個認真閱讀我文章的人,禮尚往來總是要有的,這些資料,對於【軟體測試】的朋友來說應該是最全面最完整的備戰倉庫,雖然不是什麼很值錢的東西,如果你用得到的話可以直接拿走:

如果你想學習軟體測試和需要軟體測試資料,歡迎加入扣扣交流群:731789136,裡面可以免費領取軟體測試+自動化測試資料+軟體測試面試寶典+簡歷模版+實戰專案+面試刷題工具和大佬答疑解惑,我們一起交流一起學習!

相關文章