【軟體開發】Python代碼覆蓋率100%:使用unittest進行單元測試,讓程式更可靠!

單元測試(Unit Test)是軟體開發中不可或缺的一部分,可以幫助開發人員確保程式碼的正確性、可靠性和穩定性。Python是一種廣泛使用的程式語言,提供了多種單元測試工具,其中最常用的是unittest。本文將介紹Python的unittest package,包括如何編寫測試程式(TestCase)、執行測試、斷言(Assert)以及使用測試套件(TestSuite)等。此外,本文還將介紹測試覆蓋率的概念,以及如何使用unittest來計算測試覆蓋率。

Unittest

unittest簡介

unittest是Python標準庫自帶的一個單元測試框架,可以幫助開發人員編寫和執行單元測試。unittest框架提供了許多有用的功能,包括斷言、測試用例和測試套件等。unittest框架的核心是TestCase類,這個類用於定義單元測試。TestCase類中定義了許多測試方法,開發人員可以通過繼承這個類並編寫測試方法來進行單元測試。

以下是一個簡單的例子:

import unittest

def add(a, b):
    return a + b

class TestAdd(unittest.TestCase):

    def test_add(self):
        self.assertEqual(add(1, 2), 3)
        self.assertEqual(add(0, 0), 0)

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

在這個示例中,我們定義了一個add函數,然後編寫了一個測試類TestAdd。TestAdd類繼承了unittest.TestCase類,並定義了一個測試方法test_add。在這個測試方法中,我們使用了self.assertEqual斷言來比較add函數的輸出是否符合預期。

使用unittest.TestCase定義測試項目

編寫測試程式是單元測試中的一個關鍵步驟。測試程式是指對程式碼的一個特定部分進行測試的一個單位。在Python中,我們可以通過繼承unittest.TestCase類來定義測試。

以下是一個簡單的範例:

import unittest

class TestStringMethods(unittest.TestCase):

    def test_upper(self):
        self.assertEqual('hello'.upper(), 'HELLO')

    def test_isupper(self):
        self.assertTrue('HELLO'.isupper())
        self.assertFalse('Hello'.isupper())

    def test_split(self):
        s = 'hello world'
        self.assertEqual(s.split(), ['hello', 'world'])
        # 檢查s.split的結果是否為 ['hello', 'world']

        with self.assertRaises(TypeError):
            s.split(2)
        # 檢查s.split(2)是否會引發TypeError異常

在這個示例中,我們定義了一個測試用例TestStringMethods,並定義了三個測試方法:test_upper、test_isupper和test_split。在這些測試方法中,我們使用了多種不同的斷言來檢查待測函數的輸出是否符合預期。

例如,在test_upper方法中,我們使用self.assertEqual斷言來檢查’hello’.upper()的輸出是否等於’HELLO’。在test_isupper方法中,我們使用self.assertTrue和self.assertFalse斷言來檢查’HELLO’.isupper()和’Hello’.isupper()的輸出是否為True和False。

在test_split方法中,我們使用了with self.assertRaises來檢查s.split(2)是否會引發TypeError異常。這個斷言可以確保待測函數在不正確的參數下會引發異常,從而保證待測函數的正確。

使用unittest.main()執行測試

在編寫測試程式後,我們需要執行這些測試項目,並確定待測函數的輸出是否符合預期。在Python中,我們可以通過運行unittest.main()來執行測試。 以下是一個簡單的例子:

import unittest

class TestStringMethods(unittest.TestCase):

    def test_upper(self):
        self.assertEqual('hello'.upper(), 'HELLO')

    def test_isupper(self):
        self.assertTrue('HELLO'.isupper())
        self.assertFalse('Hello'.isupper())

    def test_split(self):
        s = 'hello world'
        self.assertEqual(s.split(), ['hello', 'world'])
        # 檢查s.split的結果是否為 ['hello', 'world']

        with self.assertRaises(TypeError):
            s.split(2)
        # 檢查s.split(2)是否會引發TypeError異常

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

在這個示例中,我們執行了unittest.main(),這會自動執行TestStringMethods類中的所有測試方法。如果加入verbosity參數來執行此測試,例如:

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

unittest會提供詳細的報告,報告中包含了每個測試的名稱、狀態等信息。

斷言(assert)

斷言是單元測試中的一個重要部分,用於檢查待測函數的輸出是否符合預期。在Python中,unittest框架提供了多種斷言方法,包括以下幾種:

  • assertEqual:檢查兩個值是否相等,如果不相等,則測試失敗。
  • assertNotEqual:檢查兩個值是否不相等,如果相等,則測試失敗。
  • assertTrue:檢查一個值是否為True,如果不是,則測試失敗。
  • assertFalse:檢查一個值是否為False,如果不是,則測試失敗。
  • assertIs:檢查兩個值是否是同一個對象,如果不是,則測試失敗。
  • assertIsNot:檢查兩個值是否不是同一個對象,如果是,則測試失敗。
  • assertIn:檢查一個值是否包含在另一個值中,如果不是,則測試失敗。
  • assertNotIn:檢查一個值是否不包含在另一個值中,如果是,則測試失敗。
  • assertRaises:檢查一個函數是否會引發異常,如果不會,則測試失敗。

還有更多的assert用法可以參考此網頁。關於assert的用法,以下是一個簡單的範例:

import unittest

def add(a, b):
    return a + b

class TestAdd(unittest.TestCase):

    def test_add(self):
        self.assertEqual(add(1, 2), 3)
        self.assertNotEqual(add(0, 1), 0)

    def test_add_negative(self):
        self.assertEqual(add(-1, -2), -3)
        self.assertNotEqual(add(-1, -1), 0)

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

在這個例子中,我們定義了一個add函數,然後寫了一個測試類別TestAdd。在TestAdd類別中,我們定義了兩個測試方法test_add和test_add_negative。在這些測試方法中,我們使用了不同的斷言來檢查add函數的輸出是否符合預期。

在test_add方法中,我們使用了self.assertEqual斷言來檢查add(1, 2)的輸出是否等於3,以及add(0, 1)的輸出是否不等於0。在test_add_negative方法中,我們使用了相同的斷言方法來檢查add(-1, -2)的輸出是否等於-3,以及add(-1, -1)的輸出是否不等於0。

使用恰當的斷言對待測函數的輸出進行驗證可以幫助開發人員提高測試的精確度,從而更好地發現和修復程式碼中的錯誤。

使用unittest.TestSuite()測試套件

當測試用例的數量增加時,我們需要將這些測試用例分組並按順序執行。在Python中,我們可以通過unittest.TestSuite類別來創建測試套件,並將多個測試用例添加到測試套件中。例如以下程式範例:

import unittest

def add(a, b):
    return a + b

class TestStringMethods(unittest.TestCase):

    def test_upper(self):
        self.assertEqual('hello'.upper(), 'HELLO')

    def test_isupper(self):
        self.assertTrue('HELLO'.isupper())
        self.assertFalse('Hello'.isupper())

    def test_split(self):
        s = 'hello world'
        self.assertEqual(s.split(), ['hello', 'world'])

        with self.assertRaises(TypeError):
            s.split(2)

class TestAdd(unittest.TestCase):

    def test_add(self):
        self.assertEqual(add(1, 2), 3)
        self.assertEqual(add(0, 0), 0)

    def test_add_negative(self):
        self.assertEqual(add(-1, -2), -3)
        self.assertEqual(add(-1, 1), 0)

if __name__ == '__main__':
    suite = unittest.TestSuite()
    suite.addTest(unittest.makeSuite(TestStringMethods))
    suite.addTest(TestAdd('test_add'))
    unittest.TextTestRunner(verbosity=2).run(suite)

在這個範例中,定義了兩個測試程式TestStringMethods和TestAdd。在main函數中,我們創建了一個測試套件suite,並使用addTest()將TestStringMethods和TestAdd這兩個測試程式添加到測試套件中。其中這個範例使用了unittest.makeSuite設定要跑TestStringMethods全部的測試項目。另外這個suite只跑TestAdd中的test_add測試項目。最後,使用unittest.TextTestRunner類別來執行測試套件,並設置verbosity=2參數以顯示更詳細的報告。

使用測試套件可以將多個測試用例分組並按順序執行,從而更好地管理和執行測試。

計算代碼覆蓋率(Code Coverage)

代碼覆蓋率是衡量單元測試質量的一個指標。代碼覆蓋率是指被測試程式碼中被測試的程式碼行數佔總程式碼行數的比例。在Python中,我們可以使用coverage模組來計算測試覆蓋率。coverage package可以使用以下指令進行安裝

pip install coverage

使用方式如以下程式:

import unittest
import coverage

cov = coverage.Coverage()
cov.start()

def add(a, b):
    return a + b

class TestAdd(unittest.TestCase):

    def test_add(self):
        self.assertEqual(add(1, 2), 3)
        self.assertEqual(add(0, 0), 0)

    def test_add_negative(self):
        self.assertEqual(add(-1, -2), -3)
        self.assertEqual(add(-1, 1), 0)

cov.stop()
cov.save()

if __name__ == '__main__':
    cov.html_report()

在這個示例中,我們使用coverage.Coverage類創建了一個覆蓋率對象cov,並調用了cov.start()方法開始測試覆蓋率計算。在TestAdd測試類中,我們編寫了兩個測試方法test_add和test_add_negative。在測試結束後,我們調用了cov.stop()方法停止測試覆蓋率計算,然後使用cov.save()方法將測試覆蓋率結果保存到文件中。最後,我們使用cov.html_report()方法生成一個HTML報告,報告中包含了測試覆蓋率的詳細信息。

使用測試覆蓋率可以幫助開發人員更好地了解待測程式碼的測試情況,發現並修復未測試的代碼。測試覆蓋率是提高單元測試質量的一個重要指標。

結論

本文介紹了Python的unittest模組,包括如何編寫測試程式、執行測試、斷言以及使用測試套件等。此外,還介紹了代碼測試覆蓋率的概念,以及如何使用coverage模組來計算代碼覆蓋率。通過學習和使用unittest模組,開發人員可以更好地確保程式碼的正確性、可靠性和穩定性,提高軟體開發的質量和效率。


X. Ryan
X. Ryan

Hello!我是一個在矽谷工作,有軟體工程背景的量子計算科學家。這裡分享的內容主要是把平常研究開發時所用的小工具以及看過的東西記錄下來,同時也分享一些日常生活瑣事。

文章: 45