Blog Cover Image

Inspire you to have New thinking, Walk out your unique Road.

有的時候,你無意間遇到的一些故事,會激發你的靈感,改變你的想法,接下來你會用與之前全然不同的觀念去創造屬於你獨特的故事。

Sign @MinaYu.

[Python] 動態生成單元測試/設置環境, 移除環境

Posted on

學了一陣子實作單元測試後, 先前是前輩會先把單元測試的架構寫好讓我去實作code

但前輩走了之後,現在的單元測試已經無法符合新開發的功能,必須去研究新的方法來稍微改寫框架,這篇主要紀錄新學習的單元測試相關的功能 動態生成單元測試設置測試環境, 移除環境

我使用IDE 為 PyCharm, 單元測試的套件為 unittest, 語言 Python

動態生成單元測試

大部分撰寫單元測試的框架大致如下

from unittest import TestCase
class MyNewTest(TestCase):

    def test_function1(self):
        pass
    
    def test_function2(self):
        pass
    
    def test_function3(self):
        pass

做一個可被執行的測試函式,函式名稱開頭需為 test_

通常這樣寫 IDE 很聰明的話會顯示 綠色箭頭,接著就可以點箭頭跑測試

不然就是在terminal 下 command line 驅動單元測試


但是當我們有一些資料想要透過for 迴圈 根據值來做測試該怎麼辦?

我起初的想法就是寫for迴圈測試

    def test_function(self):
        for a in data:
            result = get_result(a)
            assertEqual(a, b)

但這樣的方法就是萬一迴圈內的某筆assert測試錯誤,整個單元測試就停住,將不會再往下測!

可是,我想要知道我迴圈內丟進去每個資料測試的狀態,那可否動態生成單元測試?

可以,而且方法很簡單(但我用錯方法找錯方向卡了四天qqq)

根據 Python unittest文件,可以使用subtest來達成功能

您可以在網站內搜尋 i=i 來找到範例函式

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)

這個範例是來驗證 0 - 6 每個數字是否可被2整除

那我們就來改寫一下

from unittest import TestCase
class MyNewTest(TestCase):

    def test_function(self):
        csv_path = "./test_data.csv"
        df = pd.read_csv(csv_path)
        for index, row in df.iterrows():
            # 可以根據值來命名單元測試的名稱唷!
            with self.subTest(row['title']):
                # 測試的code實作在這
                result = get_result(row['test_input'])
                assertEqual(expect, result)

如此一來 run 出的單元測試就可以根據想測的資料筆數來動態生成啦!

設置測試環境, 移除環境

另外一個是設置測試環境跟移除

因為測試要不留任何痕跡,而且執行單元測試時,需要先將測試功能的場景給佈置好

假設我有三個測試函式都需要設置環境跟移除環境

from unittest import TestCase
class MyNewTest(TestCase):
    # 只要繼承TestCase 就可以使用setUp, tearDown 他們是unittest內附有的功能
    def setUp(self):
        # 可以寫任何你想先設置環境的事情
        prepare(situation, value)

    def tearDown(self):
        # 結束後有沒有要刪除或者移除什麼
        os.remove("test.sqlite")

    def test_function1(self):
        pass
    
    def test_function2(self):
        pass
    
    def test_function3(self):
        pass

這樣的寫法每當我跑一個測試函式就會執行setUp 幫我佈置環境, 結束後會 testDown 移除環境

若整個Class 一起執行會跑的順序是:

setUp > test_function1 > tearDown >
setUp > test_function2 > tearDown >
setUp > test_function3 > tearDown 

可是!!! 就是有這個 可是 !

若只想佈置一次測試環境就測全部的測試函式, 全部測試測完在移除環境呢?

我們可以使用 unittest 內附的 setUpClass, tearDownClass 來實現夢想XD

這是在unittest 套件內找到的方法

    @classmethod
    def setUpClass(cls) -> None: ...
    @classmethod
    def tearDownClass(cls) -> None: ...

所以剛剛的例子可以是:

from unittest import TestCase
class MyNewTest(TestCase):
    # 只要繼承TestCase 就可以使用setUpClass, tearDownClass
    @classmethod
    def setUpClass(cls):
        # 可以寫任何你想先設置環境的事情
        prepare(situation, value)
    
    @classmethod
    def tearDownClass(cls):
        # 結束後有沒有要刪除或者移除什麼
        os.remove("test.sqlite")

    def test_function1(self):
        pass
    
    def test_function2(self):
        pass
    
    def test_function3(self):
        pass

執行的順序就會是

setUpClass > test_function1 > test_function2 > 
test_function3 > tearDownClass 

可以參考 Stackoverflow 的回答來得知setup/teardown的方法有什麼不同

你也可以進入python unittest的套件來了解哪寫函式功能可以使用

那這篇的介紹就結束囉!