1. 程式人生 > >python自動化測試三部曲之untitest框架

python自動化測試三部曲之untitest框架

終於等到十一,有時間寫部落格了,準備利用十一這幾天的假期把這個系列的部落格寫完

該系列文章本人準備寫三篇部落格

第一篇:介紹python自動化測試框架unittest

第二篇:介紹django框架+request庫實現介面測試

第三篇:介紹利用Jenkins實現持續整合

今天進入第一篇,unittest框架介紹

一、unittest簡述

unittest是python語言的單元測試框架,在python的官方文件中,對unittest單元測試框架進行了詳細的介紹,感興趣的讀者可以到https://www.python.org/doc
網站去了解;本篇部落格重點介紹unittest單元測試框架在自動化測試中的應用

unittest單元測試框架提供了建立測試用例,測試套件,和批量執行測試用例的方法,在python安裝成功後,unittest單元測試框架可以直接匯入使用,他屬於
python的標準庫;作為單元測試的框架,unittest單元測試框架也是對程式的最小模組進行的一種敏捷化的測試。在自動化測試i中,我們雖然不需要做白盒測試,
但是必須知道所使用語言的單元測試框架,這是因為後面我們測試,就會遇到用例組織的問題,雖然函數語言程式設計和麵向物件程式設計提供了對程式碼的重構,但是對於所
編寫的每個測試用例,不可能編寫成一個函式來呼叫執行;利用單元測試框架,可以建立一個類,該類繼承unittest的TestCase,這樣可以把每個TestCase看成是
一個最小的單元,由測試套件組織起來,執行時直接執行即可,同時可引入測試報告。unittest各個元件的關係如果

TestCase------------------------------->TestFixture(測試韌體)
|
|
|
|
|
|
|
|
|

TestSuite(測試套件)----------------------->TestRunner(測試執行)-------------------->TestReport(測試報告)

 

# TestCase
# 類,必須要繼承unittest.TestCase
# 一個類class繼承 unittest.TestCase,就是一個測試用例。一個TestCase的例項就是一個測試用例,就是一個完整的測試流程。
# 包括測試前環境準備setUp()|setUpClass()、執行程式碼run()、測試環境後的還原tearDown()|tearDownClass()。
# 繼承自unittest.TestCase的類中,測試方法的名稱要以test開頭。且只會執行以test開頭定義的方法(測試用例)。

 

二、測試韌體(TestFixture)

在unittest單元測試框架中,測試韌體用於處理初始化的操作,例如,在對百度的搜尋進行測試前,首先需要開啟瀏覽器並且進入百度的首頁;測試結束後,
需要關閉瀏覽器;測試韌體提哦功能了兩種執行形式,一種是每執行一個測試用例,測試韌體就會被執行一次;另外一種就不管有多少個用例i,測試韌體只會執
行一次

# 用於一個測試環境的準備和銷燬還原。
# 當測試用例每次執行之前需要準備測試環境,每次測試完成後還原測試環境,比如執行前連線資料庫、開啟瀏覽器等,執行完成後需要還原資料庫、關閉瀏覽器等操作。
# 這時候就可以啟用testfixture。

# setUp():準備環境,執行每個測試用例的前置條件;
# tearDown():環境還原,執行每個測試用例的後置條件;
# setUpClass():必須使用@classmethod裝飾器,所有case執行的前置條件,只執行一次;
# tearDownClass():必須使用@classmethod裝飾器,所有case執行完後只執行一次;

1、測試韌體每次均執行

unittest單元測試框架提供了名為setUp的tearDown的測試韌體。下面,我們通過編寫一個例子來看測試韌體的執行方式,測試程式碼如下

 1 import unittest
 2 
 3 class Test1(unittest.TestCase):
 4     
 5     # 測試韌體之前置條件
 6     def setUp(self):
 7         print("這是前置條件")
 8     
 9     # 測試韌體之後置條件
10     def tearDown(self):
11         print("這是後置條件")
12 
13     def test_case1(self):
14         print("test_case1")
15 
16     def test_case2(self):
17         print("test_case2")
18 
19 
20 if __name__ == '__main__':
21     unittest.main(verbosity=2)

 

執行結果如下

 

 

 

他的執行順序是先執行setUp方法,在執行具體的用例,最後執行tearDown方法

 

2、測試韌體只執行一次

鉤子方法setUp和tearDown雖然經常使用,但是在自動化測試中,一個系統的測試用例多達上千條,每次都執行一次的setUp和tearDown方法會耗費大量的效能,
在unittest單元測試框架中還可以使用另外一種測試韌體來解決這一問題,他就是setUpClass和tearDownClass方法,該測試韌體方法是類方法,需要在方法上
面加裝飾器@classmethod,使用該測試韌體,不管有多少個用例,測試韌體只執行一次,具體程式碼如下

 1 import unittest
 2 
 3 class Test1(unittest.TestCase):
 4     # def setUp(self):
 5     #     print("這是前置條件")
 6     #
 7     # def tearDown(self):
 8     #     print("這是後置條件")
 9     
10     @classmethod
11     def setUpClass(cls):
12         print("這是類方法前置條件")
13 
14     @classmethod
15     def tearDownClass(cls):
16         print("這是類方法後置條件")
17 
18     def test_case1(self):
19         print("test_case1")
20 
21     def test_case2(self):
22         print("test_case2")
23 
24 
25 if __name__ == '__main__':
26     unittest.main(verbosity=2)

 

結果如下

 

 

 

3、兩種測試韌體並存

import unittest


class Test1(unittest.TestCase):
    def setUp(self):
        print("這是前置條件")

    def tearDown(self):
        print("這是後置條件")

    @classmethod
    def setUpClass(cls):
        print("這是類方法前置條件")

    @classmethod
    def tearDownClass(cls):
        print("這是類方法後置條件")

    def test_case1(self):
        print("test_case1")

    def test_case2(self):
        print("test_case2")


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

 

執行結果如下

 

 

 

結果表明,先執行被@classmethod裝飾器裝飾的測試韌體,在執行普通的測試韌體 

 

三、測試執行

在以上事例中,可以看到測試用例的執行是在主函式中,unittest呼叫的是main,程式碼如下,TestProjram還是一個類,再來看該類的建構函式,程式碼如下

main = TestProgram

 

TestProjram還是一個類,再來看該類的建構函式,程式碼如下

class TestProgram(object):
    """A command-line program that runs a set of tests; this is primarily
       for making test modules conveniently executable.
    """
    # defaults for testing
    module=None
    verbosity = 1
    failfast = catchbreak = buffer = progName = warnings = None
    _discovery_parser = None

    def __init__(self, module='__main__', defaultTest=None, argv=None,
                    testRunner=None, testLoader=loader.defaultTestLoader,
                    exit=True, verbosity=1, failfast=None, catchbreak=None,
                    buffer=None, warnings=None, *, tb_locals=False):
        if isinstance(module, str):
            self.module = __import__(module)
            for part in module.split('.')[1:]:
                self.module = getattr(self.module, part)
        else:
            self.module = module
        if argv is None:
            argv = sys.argv

        self.exit = exit
        self.failfast = failfast
        self.catchbreak = catchbreak
        self.verbosity = verbosity
        self.buffer = buffer
        self.tb_locals = tb_locals
        if warnings is None and not sys.warnoptions:
            # even if DeprecationWarnings are ignored by default
            # print them anyway unless other warnings settings are
            # specified by the warnings arg or the -W python flag
            self.warnings = 'default'
        else:
            # here self.warnings is set either to the value passed
            # to the warnings args or to None.
            # If the user didn't pass a value self.warnings will
            # be None. This means that the behavior is unchanged
            # and depends on the values passed to -W.
            self.warnings = warnings
        self.defaultTest = defaultTest
        self.testRunner = testRunner
        self.testLoader = testLoader
        self.progName = os.path.basename(argv[0])
        self.parseArgs(argv)
        self.runTests()

 

在unittest模組中包含的main方法,可以方便的將測試模組轉變為可以執行的測試指令碼。main使用unittest.TestLoader類來自動查詢和載入模組內的測試用例,TestProgram類中的該部分的程式碼如下

    def createTests(self):
        if self.testNames is None:
            self.test = self.testLoader.loadTestsFromModule(self.module)
        else:
            self.test = self.testLoader.loadTestsFromNames(self.testNames,
                                                           self.module)

 

在執行測試用例時候,在main方法中加入了verbosity=2,程式碼如下

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

 

下面解釋一下verbosity部分,在verbosity中預設是1。0代表執行的測試總數和全域性結果,2代表詳細的資訊

 

四、測試套件,TestSuite

# TestSuite
# 上述簡單的測試會產生兩個問題,可不可以控制test測試用例的執行順序?若不想執行某個測試用例,有沒有辦法可以跳過?
# 對於執行順序,預設按照test的 A-Z、a-z的方法執行。若要按自己編寫的用例的先後關係執行,需要用到testSuite。
# 把多個測試用例集合起來,一起執行,就是testSuite。testsuite還可以包含testsuite。
# 一般通過addTest()或者addTests()向suite中新增。case的執行順序與新增到Suite中的順序是一致的。




1、直接執行案例

我們在func.py這個檔案中定義加減乘除4個測試函式
#Auther Bob
#--*--conding:utf-8 --*--
def add(a,b):
    return a + b
def minus(a,b):
    return a - b
def multi(a,b):
    return a * b
def divide(a,b):
    return a / b

 

 

 

 

然後在myunittest.py檔案中定義我們的測試程式碼,這裡用到了斷言,我們後面會介紹

from test1 import func

class Test2(unittest.TestCase):
    def setUp(self):
        print("前置條件")

    def tearDown(self):
        print("後置條件")

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

    def test_minus(self):
        self.assertEqual(4,func.minus(5,1))

    def test_multi(self):
        self.assertEqual(4,func.multi(2,2))

    def test_divide(self):
        self.assertEqual(10,func.divide(100,10))


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

 

執行結果如下

 

 

 

2、新增案例到測試套件中

上述簡單的測試會產生兩個問題,可不可以控制test測試用例的執行順序?若不想執行某個測試用例,有沒有辦法可以跳過?
對於執行順序,預設按照test的 A-Z、a-z的方法執行。若要按自己編寫的用例的先後關係執行,需要用到testSuite。
把多個測試用例集合起來,一起執行,就是testSuite。testsuite還可以包含testsuite。
一般通過addTest()或者addTests()向suite中新增。case的執行順序與新增到Suite中的順序是一致的。


如果用到測試套件TestSuite,則需要先寫好測試程式碼,但是先不要執行

我們同樣在myunittest.py檔案中定義我們的測試程式碼
from test1 import func


class Test3(unittest.TestCase):
    def setUp(self):
        print("前置條件")

    def tearDown(self):
        print("後置條件")


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


    def test_minus(self):
        self.assertEqual(4,func.minus(5,1))


    def test_multi(self):
        self.assertEqual(4,func.multi(2,2))

    def test_divide(self):
        self.assertEqual(10,func.divide(100,10))

 

我們在test_suit.py檔案中引入測試案例,然後通過TestSuite類的addTests方法把測試用例新增到測試套件中

import unittest
from test1.myunittest import Test3
# from test1.myunittest2 import Test3 as t3



if __name__ == '__main__':
    # 輸出資訊到控制檯
    
    # 例項化一個TestSuite類
    suite = unittest.TestSuite()
    
    # 把需要執行的案例放在一個list中
    tests = [Test3("test_add"), Test3("test_minus"), Test3("test_multi"), Test3("test_divide")]
    
    # 把案例新增到例項化好的測試套件中
    suite.addTests(tests)
    # t = [t3("test_add"), t3("test_minus"), t3("test_multi"), t3("test_divide")]
    # suite.addTests(tests)
    
    # 例項化一個引數執行類
    runner = unittest.TextTestRunner(verbosity=2)
    
    # 測試執行類的例項執行測試套件
    runner.run(suite)

以上的案例我們是新增一個檔案的測試案例,我們同樣可以新增多個檔案中的案例到一個測試套件中,然後執行這個測試套件即可
import unittest
from test1.myunittest import Test3
from test1.myunittest2 import Test3 as t3



if __name__ == '__main__':
    # 輸出資訊到控制檯

    # 例項化一個TestSuite類
    suite = unittest.TestSuite()

    # 把需要執行的案例放在一個list中
    tests = [Test3("test_add"), Test3("test_minus"), Test3("test_multi"), Test3("test_divide")]

    # 把案例新增到例項化好的測試套件中
    suite.addTests(tests)
    
    # 新增另外一個檔案中的測試案例到測試套件中
    t = [t3("test_add"), t3("test_minus"), t3("test_multi"), t3("test_divide")]
    suite.addTests(t)

    # 例項化一個引數執行類
    runner = unittest.TextTestRunner(verbosity=2)

    # 測試執行類的例項執行測試套件
    runner.run(suite)

 

上面的執行方式是輸出結果到控制檯,我們也可以輸出結果到檔案中

import unittest
from test1.myunittest import Test3
from test1.myunittest2 import Test3 as t3



if __name__ == '__main__':
    
    # 輸出資訊到txt檔案中
    suite = unittest.TestSuite()
    tests = [Test3("test_add"), Test3("test_minus"), Test3("test_multi"), Test3("test_divide")]
    suite.addTests(tests)
    t = [t3("test_add"), t3("test_minus"), t3("test_multi"), t3("test_divide")]
    suite.addTests(t)
    with open('UnittestTextReport.txt', 'a') as  f:
        runner = unittest.TextTestRunner(stream=f, verbosity=2)
        runner.run(suite)

 

3、直接新增測試類到測試套件中

案例一個一個新增還是比較麻煩,我們可以直接新增一個測試類到測試套件中

利用下面的方法載入一個測試類

unittest.TestLoader().loadTestsFromTestCase(t3)

 

import unittest
from unittest import TestLoader

from test1 import myunittest

from test1.myunittest2 import Test3 as t3

if __name__ == '__main__':
    suite = unittest.TestSuite()
    loader = TestLoader()
    test_cases1 = unittest.TestLoader().loadTestsFromTestCase(t3)
    # 引數是一個類,而這個類必須是unittest.TestCase的子類或者孫類
    suite.addTests(test_cases1)
    runner = unittest.TextTestRunner(verbosity=2)
    runner.run(suite)

 

4、直接載入一個模組到測試套件中,如果這個模組中有多個類,則會把所有的類的測試案例載入到測試套件中

unittest.TestLoader().loadTestsFromModule(myunittest)

 

import unittest
from unittest import TestLoader

from test1 import myunittest

from test1.myunittest2 import Test3 as t3

if __name__ == '__main__':
    suite = unittest.TestSuite()
    loader = TestLoader()
    test_cases1 = unittest.TestLoader().loadTestsFromModule(myunittest)
    # 引數是一個模組,會把這個模組裡的所有case載入進來
    suite.addTests(test_cases1)
    runner = unittest.TextTestRunner(verbosity=2)
    runner.run(suite)

 

我給大家截圖看下

 

 

 

5、通過案例名稱新增案例到測試套件中

    test_cases1 = unittest.TestLoader().loadTestsFromName('test1.myunittest2.Test3.test_minus')

 

import unittest
from unittest import TestLoader

from test1 import myunittest

from test1.myunittest2 import Test3 as t3

if __name__ == '__main__':

    suite = unittest.TestSuite()
    loader = TestLoader()
    test_cases1 = unittest.TestLoader().loadTestsFromName('test1.myunittest2.Test3.test_minus')
    # 載入某個cese
    runner = unittest.TextTestRunner(verbosity=2)
    suite.addTests(test_cases1)
    runner.run(suite)

 

我截圖給大家看下目錄結構

 

 

 

五、忽略執行案例

在實際的專案中,有些案例我們可能暫時不需要執行,如果有這樣的問題,我們該怎麼辦,unittest框架已經為我們提供瞭解決方案

 

1、無條件跳過該案例,用該裝飾器修飾要執行的案例,則該案例會被忽略不執行

@unittest.skip("do not exec")

 

    @unittest.skip("do not exec")
    # 無條件跳過執行該案例
    def test_add(self):
        self.assertEqual(3,func.add(1,2))

 

2、滿足某個條件才跳過該案例

@unittest.skipIf(4 > 3,"2 > 3 do not exec")

 

    @unittest.skipIf(4 > 3,"2 > 3 do not exec")
    # 滿足某個條件才跳過執行
    def test_minus(self):
        self.assertEqual(4,func.minus(5,1))

 

3、不滿足某個條件才跳過案例

@unittest.skipUnless(4 < 3,"hahah")

 

    @unittest.skipUnless(4 < 3,"hahah")
    # 不滿足某個條件才跳過執行
    def test_multi(self):
        self.assertEqual(4,func.multi(2,2))

 

4、我們也可以在案例裡面定義忽略執行這條案例

    def test_divide(self):
        self.skipTest("wydd")
        self.assertEqual(10,func.divide(100,10))

 

 

六、斷言

斷言就是判斷實際測試結果與預期結果是否一致,一致則測試通過,否則失敗。因此,在自動化測試中,無斷言的測試用例是無效的,這是因為當一個功能自動化已經全部實現,在每次版本迭代中執行測試用例時,執行的結果必須是權威的,也就是說自動化測試用例執行結果應該無功能性或者邏輯性問題,在自動化測試中最忌諱的就是自動化測試的用例雖然是通過的,但是被測試的功能卻是存在問題的,自動化測試用例經常應用在迴歸測試中,發現的問題不是特別多,如果測試結果存在功能上的問題,則投入了人力去做的自動化引數就沒有多大的意義了,所以每一個測試用例必須要有斷言;在測試的結果中只有兩種可能,一種是執行通過,另外一種是執行失敗,也就是功能存在問題,在TestCase類中提供了assert方法來檢查和報告失敗,常用的方法如下

     self.assertEqual(3,func.add(1,2))
        # 判斷是否相等
        
        self.assertNotEqual()
        # 判斷是否不等於
        
        self.assertTrue()
        # 判斷布林值是否為True
        
        self.assertFalse()
        # 判斷布林值是否為False
        
        self.assertIs()
        # 判斷型別是否相同
        
        self.assertIsNot()
        # 判斷型別是否不同
        
        self.assertIsNone()
        # 判斷是否為None
        
        self.assertIsNotNone()
        # 判斷是否不為None
        
        self.assertIn()
        # 判斷在某個範圍內
        
        self.assertNotIn()
        # 判斷是否不在某個範圍內
        
        self.assertIsInstance()
        # 判斷是否為某個類的例項
        
        self.assertNotIsInstance()