unittest筆記

微微微笑發表於2015-11-25

背景

1. 學習資料: 

官網: https://docs.python.org/2.7/library/unittest.html

https://docs.python.org/3/library/unittest.html

IBM Python自動單元測試框架: http://www.ibm.com/developerworks/cn/linux/l-pyunit/

2. python其他類似的工具

doctest: https://docs.python.org/2.7/library/doctest.html#module-doctest

unittest2: https://pypi.python.org/pypi/unittest2

nose: https://nose.readthedocs.org/en/latest/

3. 定義

unittest,有時也叫"PyUnit",是Python語言版本的單元測試框架。和JAVA語言的JUnit類似。

 

unittest 主要分為以下幾個模組:

test fixture:

指的是測試之前需要做的準備工作以及相關的清理工作。比如,新建暫時資料庫或代理資料庫,目錄,或者啟動伺服器程式。這裡主要就是指 setup 以及teardown 函式。

test case(TestCase):
test case是測試的最小單元。它檢查一組特定輸入的結果。unittest提供一個基類,TestCase,我們可以使用TestCase來新建新的測試用例。

test suite(TestSuite):
test suite是test case的集合,也可以是test suite的集合。它用來將所有一起執行的測試整合在一起。

test runner(TestRunner):
test runner負責執行測試並且提供測試結果。runner可能使用圖形介面、文字介面、或者返回一個特殊值來指定測試結果。

示例

1. 測試用例組成(TestCase)

所有的測試用例必須繼承自類 unittest.TestCase ,一般測試用例方法名以 test開頭。一個測試用例類內部可以定義一個或多個測試用例方法,比如下面就定義了3個方法。

import unittest

class TestStringMethods(unittest.TestCase):
    
    def setUp(self):
        print("This is setup")

    def test_upper(self):
        print("This is test_upper")
        self.assertEqual('foo'.upper(), 'FOO')

    def test_isupper(self):
        print("This is test_isupper")
        self.assertTrue('FOO'.isupper())
        self.assertFalse('Foo'.isupper())

    def test_split(self):
        print("This is test_split")
        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)
            
    def tearDown(self):
        print("This is tearDown")

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

指令碼上右鍵,選擇 Run -> Run as Python unit-test ,就可以看到控制檯輸出如下:

Finding files... done.
Importing test modules ... done.

This is setup
This is test_isupper
This is tearDown
This is setup
This is test_split
This is tearDown
This is setup
This is test_upper
This is tearDown
----------------------------------------------------------------------
Ran 3 tests in 0.004s

OK

可以看到,每個測試用例方法執行之前都會呼叫 setUp方法,每個測試用例方法執行結束都會呼叫 tearDown方法。

一般會把測試用例執行前的準備工作放到setUp方法裡面,比如WEB測試的登入操作。setUp執行時如果丟擲異常,後邊的test都不會被執行,該用例會被標記為error。

一般會把測試用例執行後的清理工作放到tearDown方法裡面,比如WEB測試的關閉瀏覽器操作。只要setUp執行成功,tearDown就會被執行。tearDown丟擲異常的話,測試結果會被標記為error。

2. 測試結果判定

 unittest.TestCase 類包含各種判定測試結果的方法。

比如上面我們使用過的“self.assertEqual('foo'.upper(), 'FOO')”。

MethodChecks thatNew in
assertEqual(a, b) == b  
assertNotEqual(a, b) != b  
assertTrue(x) bool(x) is True  
assertFalse(x) bool(x) is False  
assertIs(a, b) is b 3.1
assertIsNot(a, b) is not b 3.1
assertIsNone(x) is None 3.1
assertIsNotNone(x) is not None 3.1
assertIn(a, b) in b 3.1
assertNotIn(a, b) not in b 3.1
assertIsInstance(a, b) isinstance(a, b) 3.2
assertNotIsInstance(a, b) not isinstance(a, b) 3.2

 

所有的assert方法都含有一個預設引數“msg”,該引數預設值為“None”。如果給msg傳值,在assert判定fail的時候,系統就會列印出msg作為fail message。

“self.assertEqual('foo'.upper(), 'FOO1',"foo.upper is not FOO1!")”

如果assert方法判定特定條件不滿足,就會丟擲一個異常 AssertionError,unittest執行結果就會被判定為 fail。

3. 測試用例集(TestSuite)

多個TestCase可以組成一個TestSuite 。一般實際測試都有很多個TestCase,我們需要對其進行組合,這樣方便執行以及收集結果。

跑一個TestSuite ,和單獨跑TestSuite裡面的所有的TestCase ,結果是一樣的。

多個TestSuite 也可以組成一個更大的TestSuite。

import unittest

class TestStringMethods(unittest.TestCase):
    # 類內容參見上面的程式碼
        
def suite():
    suite = unittest.TestSuite()
    suite.addTest(TestStringMethods('test_upper'))
    suite.addTest(TestStringMethods('test_split'))
    return suite

if __name__ == '__main__':
    print(suite())

執行結果如下:

<unittest.suite.TestSuite tests=[<__main__.TestStringMethods testMethod=test_upper>, 
<__main__.TestStringMethods testMethod=test_split>]>

可以看到suite()返回的是一個 TestSuite例項,裡面只含有我們新增進去的測試用例方法。

TestSuite 主要包含4個方法:

addTest(test):新增test到TestSuite。這裡的test可以是一個TestCase,也可以是一個TestSuite。

addTests(tests):新增多個test到TestSuite。這裡的tests 可以是一個 TestCase列表,也可以是一個TestSuite列表。

run(result):跑TestSuite,並且收集測試結果到result。

countTestCases():返回該TestSuite說包含的所有的TestCase數目。

4. TestLoader

該類的目的就是為了更方便的整合所有testCase 為 TestSuite。

一般都不需要例項化一個TestLoader物件,可以直接通過 unittest.defaultTestLoader 來獲得一個 TestLoader 例項。

 

該類主要包含的都是loadTest方法,就是通過各種方式來收集TestCase 。 方法返回的是一個TestSuite 例項 。

loadTestsFromTestCase(testCaseClass):通過TestCase的類名載入用例。比如 TestStringMethods 。

loadTestsFromModule(module, pattern=None):通過模組名載入用例。

loadTestsFromName(name, module=None):通過名稱載入用例。該名稱一般是“xxx.xxxx.xxx.xxx”的形式,可以具體到TestCase類名(比如 TestStringMethods),也可以具體到類中的方法名(比如 test_split )。

loadTestsFromNames(names, module=None):通過多個名稱載入用例。類似 loadTestsFromName ,只不過這裡的引數是一個集合,比如一個列表。

discover(start_dir, pattern='test*.py', top_level_dir=None):通過路徑載入用例。 start_dir 一般是一個大的路徑。pattern一般是具體指令碼的匹配模式,預設是所有以test開頭的指令碼。

使用示例:

import unittest

class TestStringMethods(unittest.TestCase):
    #類內容參見上面的程式碼
        
def suite():
    suite = unittest.defaultTestLoader.loadTestsFromTestCase(TestStringMethods)
    return suite

if __name__ == '__main__':
    print(suite())

輸出結果如下:

<unittest.suite.TestSuite tests=[<__main__.TestStringMethods testMethod=test_isupper>, 
<__main__.TestStringMethods testMethod=test_split>,
<__main__.TestStringMethods testMethod=test_upper>]>

可以看到返回的是一個TestSuite例項。

 5. TestResult

 該類用來收集測試結果。所有的測試結果都可以通過TestResult獲取。

import unittest

class TestStringMethods(unittest.TestCase):
    #類內容參見上面的程式碼

def suite():
    suite =unittest.defaultTestLoader.loadTestsFromTestCase(TestStringMethods)
    return suite

if __name__ == '__main__':
    my_result=unittest.TestResult()
    my_suite=suite()
    my_suite.run(my_result)
    print("My Result:",my_result)
    print("Errors:",my_result.errors)
    print("Failures:",my_result.failures)
    print("Total Run case:",my_result.testsRun)

輸出結果:

My Result: <unittest.result.TestResult run=3 errors=0 failures=1>
Errors: []
Failures: [(<__main__.TestStringMethods testMethod=test_upper>, 'Traceback (most recent call last):\n  File "E:\\workspace\\Test\\src\\test\\test_unittest.py", line 10, in test_upper\n    self.assertEqual(\'foo\'.upper(), \'FOO1\',"oooFail")\nAssertionError: \'FOO\' != \'FOO1\'\n- FOO\n+ FOO1\n?    +\n : oooFail\n')]
Total Run case: 3

errors : 返回一個列表。如果測試Pass,則列表為空。不然就是所有的error集合。

failures  : 返回一個列表。如果測試Pass,則列表為空。不然就是所有的failure集合。比如上面我故意更改 test_upper的assert判定條件,導致該用例fail。這裡就記錄了failure的情況。

testsRun:返回一個整數。值為所有的run的測試用例數目。比如這裡就是3。

TestResult 還含有一些方法,感覺主要作用就是定義自己的TestResult時候可以重寫這些方法。

startTest(test) :將要執行測試用例方法的時候呼叫。

stopTest(test) :測試用例方法執行完畢後呼叫。

startTestRun() :執行第一個用例前呼叫。

stopTestRun() :執行最後一個用例後呼叫。

addError(test, err) :測試用例返回error時呼叫。

addFailure(test, err) :測試用例返回fail時呼叫。

addSuccess(test) :測試用例返回pass時呼叫。

6. 命令列執行

示例:

python -m unittest test_module1 test_module2
python -m unittest test_module.TestClass
python -m unittest test_module.TestClass.test_method
python -m unittest tests/test_something.py

可以採用 “python -m unittest xxxx” 的方式來執行測試用例。 xxxx可以是模組、用例類、用例類中的方法、或者檔案路徑。

>python -m unittest test_unittest.py
...
----------------------------------------------------------------------
Ran 3 tests in 0.001s

OK

如果想獲取更詳細的內容,可以加 -v 。

>python -m unittest -v test_unittest.py
test_isupper (tet_excel.TestStringMethods) ... ok
test_split (tet_excel.TestStringMethods) ... ok
test_upper (tet_excel.TestStringMethods) ... ok

----------------------------------------------------------------------
Ran 3 tests in 0.002s

OK

也可以使用discover來自動載入測試用例。但是前提是指令碼名稱必須以 test開頭。

discover 命令還有其他的一些使用方法,比如自定義匹配模式等等。這裡不細說。

>cd project_directory

>python -m unittest discover
...
----------------------------------------------------------------------
Ran 3 tests in 0.001s

OK

7. 跳過執行

如果某個測試方法不想執行,給它加上 skip()裝飾器即可。

示例:

import unittest
import sys
from common import mylib

class MyTestCase(unittest.TestCase):

    @unittest.skip("demonstrating skipping")
    def test_nothing(self):
        self.fail("shouldn't happen")

    @unittest.skipIf(mylib.__version__< 5,
                     "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

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

執行結果如下:

test_format (__main__.MyTestCase) ... skipped 'not supported in this library version'
test_nothing (__main__.MyTestCase) ... skipped 'demonstrating skipping'
test_windows_support (__main__.MyTestCase) ... ok

----------------------------------------------------------------------
Ran 3 tests in 0.001s

OK (skipped=2)

可以看到前面兩個用例都被跳過了。但是因為我的環境時window的,所以第3個用例沒有被跳過。

如果某個測試用例類不想執行,也可以跳過。

import unittest

@unittest.skip("showing class skipping")
class MySkippedTestCase(unittest.TestCase):
    def test_not_run(self):
        pass
    
    def test_not_run2(self):
        pass

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

執行結果如下:

test_not_run (__main__.MySkippedTestCase) ... skipped 'showing class skipping'
test_not_run2 (__main__.MySkippedTestCase) ... skipped 'showing class skipping'

----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK (skipped=2)

可以看到兩個測試用例方法都被跳過了。

8. TestProgram

關於命令列執行的一個測試類。沒看到官方文件有詳細的資料,暫時不研究。

相關文章