1. 程式人生 > >33.Python的單元測試工具—— unittest(高階)

33.Python的單元測試工具—— unittest(高階)

這篇部落格詳細介紹Python的unittest模組內部的類及方法的更多內容。

unittest.TestCase類

class unittest.TestCase(methodName='runTest')
這個類的例項表示一個測試用例,預設的methodNamerunTest,即最簡單的測試用例類的定義只包含runTest方法的定義。如果同時定義了runTest方法和以test開頭命名的方法,會忽略runTest方法。如果要指定執行某些方法,可以這樣:

suite = unittest.TestSuite()
suite.addTest(Test('test_al'))
unittest.TextTestRunner(verbosity=2
).run(suite)

TestCase類中定義的方法分為三大類:測試執行;結果檢查及錯誤上報;查詢測試用例資訊。

測試執行

setUp()

在執行每個測試用例之前被執行,任何異常(除了unittest.SkipTestAssertionError異常以外)都會當做是error而不是failure,且會終止當前測試用例的執行。

tearDown()

執行了setUp()方法後,不論測試用例執行是否成功,都執行tearDown()方法。如果tearDown()的程式碼有異常(除了unittest.SkipTestAssertionError異常以外),會多算一個error。

setUpClass(cls)與tearDownClass(cls)

測試用例被執行前、後執行的方法,定義時必須加上classmethod裝飾符,比如:

import unittest

class MyTestCase(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        print 'set up class ran'

    def setUp(self):
        print 'set up test case ran'

    def test_equal(self):
        self.assertEqual(1
, 1, '1 not equals 1') def test_true(self): self.assertTrue('LOO'.isupper(), 'LOO not upper') def tearDown(self): print 'tear down test case ran' @classmethod def tearDownClass(cls): print 'tear down class ran'

測試結果如下:

$ python -m unittest unit
set up class ran
set up test case ran
tear down test case ran
.set up test case ran
tear down test case ran
.tear down class ran

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

OK

skipTest(reason)

在某個測試用例函式的定義中呼叫skipTest(reason)會忽略這個測試用例的執行,在setUp()方法中呼叫,會忽略所有測試用例的執行。其實skipTest(reason)函式丟擲的就是unittest.SkipTest異常。

run(result=None)

執行一個測試用例,將測試結果收集到result變數中,測試結果不返回給呼叫者。如果result引數的值為None,則測試結果在下面提到的defaultTestResult()方法的返回值中。比如:

import unittest

class Sample(unittest.TestCase):
    def test_a(self):
        assert 1 == 2, 'test print Errors'
        print 'test_a'

if __name__ == '__main__':
    r = unittest.TestResult()
    Sample('test_a').run(result=r)
    print r.__dict__

執行結果如下:

這裡寫圖片描述

debug()

與run方法將測試結果儲存到result變數中不同,debug方法執行測試用例將異常資訊上報給呼叫者。

結果檢查及錯誤上報

Method Checks that
assertEqual(a, b) a == b
assertNotEqual(a, b) a != b
assertTrue(x) bool(x) is True
assertFalse(x) bool(x) is False
assertIs(a, b) a is b
assertIsNot(a, b) a is not b
assertIsNone(x) x is None
assertIsNotNone(x) x is not None
assertIn(a, b) a in b
assertNotIn(a, b) a not in b
assertIsInstance(a, b) isinstance(a, b)
assertNotIsInstance(a, b) not isinstance(a, b)
assertAlmostEqual(a, b) round(a-b, 7) == 0
assertNotAlmostEqual(a, b) round(a-b, 7) != 0
assertGreater(a, b) a > b
assertGreaterEqual(a, b) a >= b
assertLess(a, b) a < b
assertLessEqual(a, b) a <= b
assertRegexpMatches(s, r) r.search(s)
assertNotRegexpMatches(s, r) not r.search(s)
assertItemsEqual(a, b) sorted(a) == sorted(b) and works with unhashable objs

上面所有的方法都支援新增第三個字串引數,用於出錯時的資訊展示。

assertIsInstance(a, b)與assertNotIsInstance(a, b)

assertIsInstance(a, b)和assertNotIsInstance(a, b)中的型別b,既可以是一個型別,也可以是型別組成的元組。

assertAlmostEqual與assertNotAlmostEqual

assertAlmostEqual(first, second, places=7, msg=None, delta=None)
assertNotAlmostEqual(first, second, places=7, msg=None, delta=None)

判斷兩個值是否約等於或者不約等於,places表示小數點後精確的位數。比如,如果精確到小數點後兩位,1.112和1.113是相等的:

import unittest

class Test(unittest.TestCase):
    def test_al(self):
        self.assertAlmostEqual(1.111, 1.112, places=2)

這裡寫圖片描述

如果精確到後3位,則是不相等的:
這裡寫圖片描述
如果提供了delta引數,則不能同時提供places引數(這時比較兩個變數的差值<=delta或者>delta)。

assertItemsEqual(a, b)

比較ab的元素相同,且不忽略重複的元素,比如[2, 3, 1]與[3, 2, 1]是items equal的,但是[2, 2, 1]和[2, 1]不是item equal的。

addTypeEqualityFunc(typeobj, function)

如果有自定義的類,這個函式可以為自定義的類提供相等性檢查方法,比如:

import unittest

class Mars(object):
    def __init__(self):
        self.value = 2

    def change_to(self, value):
        self.value = value

    def get_value(self):
        return self.value

def looFunc(first, second, msg=None):
    if first.get_value() != second.get_value():
        raise unittest.TestCase.failureException(msg)

class MarsTest(unittest.TestCase):
    def __init__(self, methodName):
        super(MarsTest, self).__init__(methodName)
        self.addTypeEqualityFunc(Mars, looFunc)

    def test_my(self):
        a = Mars()
        b = Mars()
        b.change_to(6)
        self.assertEqual(a, b, 'a not equal b')

執行結果如下:
這裡寫圖片描述

檢查程式中應該丟擲的異常資訊

Method Checks that
assertRaises(exc, fun, *args, **kwds) fun(*args, **kwds) raises exc
assertRaisesRegexp(exc, r, fun, *args,**kwds) fun(*args, **kwds) raises exc and the message matches regex r

檢查異常的方法是沒有錯誤資訊提示引數的。如果期望的異常有多個,也可以給exc引數一個多個異常組成的元組,比如:

import unittest, random

def raise_exec1():
    raise Exception('mars')

def raise_exec2():
    raise random.choice([NameError('[3] failed'), TypeError('[1]')])

class MyTestCase(unittest.TestCase):
    def test_exec(self):
        self.assertRaises(Exception, raise_exec1)

    def test_exec2(self):
        self.assertRaisesRegexp((NameError, TypeError), r'\[\d\]\w*', raise_exec2)

如果只提供exc引數也是可以的,這時返回一個上線文管理器,可以配合with語句把要呼叫的函式放在上線文管理器中呼叫,並且unittest將被呼叫函式返回的異常賦值給上下文管理器的返回值的exception屬性,比如:

import unittest

class marsException(Exception):
    error_code = 3

def raise_exec1():
    raise marsException('mars')

class MyTestCase(unittest.TestCase):
    def test_exec1(self):
        with self.assertRaises(marsException) as cm:
            raise_exec1()
        self.assertEqual(cm.exception.error_code, 3,
                'Error code error')

測試結果如下:

$ python -m unittest -v unit
test_exec1 (unit.MyTestCase) ... ok

----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

fail(msg=None)

無條件宣告一個測試用例失敗,msg是失敗資訊。

failureException(msg)

是unittest.TestCase的屬性,用來表示失敗的異常,預設被賦值為AssertionError。

longMessage

預設被賦值為False,如果賦值為True,可以在結果中包含更詳細的diff資訊。

maxDiff

預設長度80*8,用來控制diff顯示的長度。

測試用例資訊查詢

countTestCases()

返回測試用例的個數,對於TestCase例項來說,這個返回值一直是1.

defaultTestResult()

如果在run()方法中未提供result引數,該函式返回一個包含本用例測試結果的TestResult物件。

id()

返回測試用例的編號,通常是如下格式:模組名.類名.函式名。可以用於測試結果的輸出。

shortDescription()

返回測試用例的描述,即函式的docstring,如果沒有,返回None。可以用於測試結果輸出中描述測試內容。

addCleanup(function, *args, **kwargs)

新增針對每個測試用例執行完tearDown()方法之後的清理方法,新增進去的函式按照後進先出(LIFO)的順序執行,當然,如果setUp()方法執行失敗,那麼不會執行tearDown()方法,自然也不會執行addCleanup()裡新增的函式。

doCleanups()

無條件強制呼叫addCleanup()新增的函式,適用於setUp()方法執行失敗但是需要執行清理函式的場景,或者希望在tearDown()方法之前執行這些清理函式。

TestSuite類

class unittest.TestSuite(tests=())
TestSuite類用於將測試用例分組,比如實際工作中需要將測試用例按照優先順序分類。tests引數是一個可迭代的物件,每個物件可以是測試用例,也可以是測試套,比如:

import unittest

class A(unittest.TestCase):
    def test_a(self):
        print 'test_a'

class B(unittest.TestCase):
    def test_b(self):
        print 'test_b'

    def test_c(self):
        print 'test_c'

if __name__ == '__main__':
    suite1 = unittest.TestSuite(tests=[B('test_c'), A('test_a')])
    suite = unittest.TestSuite(tests=(suite1, B('test_b')))
    unittest.TextTestRunner(verbosity=2).run(suite)

執行結果如下:
這裡寫圖片描述
TestSuite類中定義瞭如下方法:

addTest(test)

新增測試用例,test引數可以是一個TestCase例項或者TestSuite例項。

addTests(tests)

tests引數是一個由測試用例或測試套組成的可迭代物件。

run(result)

執行測試套中包含的用例,將結果儲存到result引數對應的TestResult物件中,比如:

import unittest

class A(unittest.TestCase):
    def test_a(self):
        print 'test_a'

class B(unittest.TestCase):
    def test_b(self):
        print 'test_b'

    def test_c(self):
        print 'test_c'

if __name__ == '__main__':
    suite1 = unittest.TestSuite(tests=[B('test_c'), A('test_a')])
    suite = unittest.TestSuite()
    suite.addTest(suite1)

    r = unittest.TestResult()
    suite.run(r)
    print r.__dict__

執行結果如下:
這裡寫圖片描述

debug()

與TestCase中的debug()中的功能相同,執行測試用例,如果有異常,將異常上報給呼叫者。

countTestCases()

返回測試套中測試用例的數量,比如:

import unittest

class A(unittest.TestCase):
    def test_a(self):
        print 'test_a'

class B(unittest.TestCase):
    def test_b(self):
        print 'test_b'

    def test_c(self):
        print 'test_c'

if __name__ == '__main__':
    suite1 = unittest.TestSuite(tests=[B('test_c'), A('test_a')])
    suite = unittest.TestSuite(tests=(suite1, B('test_b')))
    print suite.countTestCases()

執行結果為:3。

如果覺得我的文章對您有幫助,歡迎關注我(CSDN:Mars Loo的部落格)或者為這篇文章點贊,謝謝!