1. 程式人生 > >暢談python之單元測試框架-unittest

暢談python之單元測試框架-unittest

一. unittest最核心的四個概念

unittest中最核心的四個概念是:test case,test suite,test runner,test fixture

技術分享圖片

TestCase:一個testcase的例項就是一個測試用例:測試前準備環境的搭建(setUp),執行測試程式碼(run),以及測試後環境的還原(tearDown)

TestSuite:多個測試用例集合在一起

TestLoader:是用來載入TestCase到TestSuite中的

TextTestRunner:用來執行測試用例的。其中的run(test)會執行TestSuite/TestCase中的run(result)方法

TextTestResult:儲存TextTestRunner執行的測試結果

fixture:測試用例環境的搭建和銷燬(setUp/setUpClass,tearDown/tearDownClass)

二. unittest初級使用

1. 匯入unittest模組、被測檔案或者其中的類

2. 建立一個測試類,並繼承unittest.TestCase

3. 重寫setUp和tearDown方法(如果有初始化條件和結束條件)

4. 定義測試函式,函式以test_開頭

5. 在函式體中使用斷言來判斷測試結果是否符合預期結果

6. 呼叫unittest.main()方法來執行測試用例

例項

1. 在工程下建立一個My_UnitTest的包,在這個包下面,建立一個被測物件myClass.py和一個測試用例Test_Myclass.py

技術分享圖片

2. 如果要測試數學中的加法和減法,測試物件myClass.py的內容為

class Math:

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

    def minus(self, a, b):
        return a - b

3. 在測試用例中引入unittest,被測檔案中的類,注意測試用例是以test_開頭

import unittest
from revise.My_UnitTest.myClass import Math

class Test_MyClass(unittest.TestCase):

    def setUp(self):
        self.m = Math()

    def tearDown(self):
        pass

    def test_add(self):
        result = self.m.add(100, 23)
        self.assertEqual(123, result)

    #減法
    def test_minus(self):
        result = self.m.minus(235, 111)
        self.assertEqual(124, result)

4. 測試用例的執行順序是以字母a-z和數字的從小到大的順序來排列的,所以上述兩個測試用例執行的順序是add在前,minus再後,可以改改程式碼驗證一下:

import unittest
from revise.My_UnitTest.myClass import Math

class Test_MyClass(unittest.TestCase):

    def setUp(self):
        self.m = Math()

    def tearDown(self):
        pass

    def test_add(self):
        result = self.m.add(100, 23)
        print("我先執行")
        self.assertEqual(123, result)

    #減法
    def test_minus(self):
        result = self.m.minus(235, 111)
        print("我後執行")
        self.assertEqual(124, result)

執行結果:

Testing started at 12:36 ...
D:\Program\python34\python.exe "D:\Program\PyCharm 2018.1.4\helpers\pycharm\_jb_unittest_runner.py" --path D:/python_workshop/python6/revise/My_UnitTest/Test_Myclass.py
Launching unittests with arguments python -m unittest D:/python_workshop/python6/revise/My_UnitTest/Test_Myclass.py in D:\python_workshop\python6\revise\My_UnitTest
我先執行
我後執行


Ran 2 tests in 0.001s

OK

如果希望minus先執行,add後執行,可以給前者函式名加一個1,後者加一個2

    def test_2_add(self):
        result = self.m.add(100, 23)
        print("我後執行")
        self.assertEqual(123, result)

    #減法
    def test_1_minus(self):
        result = self.m.minus(235, 111)
        print("我先執行")
        self.assertEqual(124, result)

5. 如果在setUp裡實例化被測檔案中的類,那麼每一條測試用例都要例項化一次被測類,用setUpClass()可以只例項化一次,同理,tearDownClass也只做一次收尾工作

    @classmethod
    def setUpClass(cls):
        cls.m = Math()

    @classmethod
    def tearDownClass(cls):
        pass

6. 用unittest.main()的方法來執行測試用例Test_Myclass.py

import unittest
from revise.My_UnitTest.myClass import Math

class Test_MyClass(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        cls.m = Math()

    @classmethod
    def tearDownClass(cls):
        pass

    def test_2_add(self):
        result = self.m.add(100, 23)
        print("我後執行")
        self.assertEqual(123, result)

    #減法
    def test_1_minus(self):
        result = self.m.minus(235, 111)
        print("我先執行")
        self.assertEqual(124, result)


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

三. 斷言Assert

TestCase類提供了一系列的斷言,即結果比對的函式

Method

Checks that

New in

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

3.1

assertIsNot(a, b)

a is not b

3.1

assertIsNone(x)

x is None

3.1

assertIsNotNone(x)

x is not None

3.1

assertIn(a, b)

a in b

3.1

assertNotIn(a, b)

a not in b

3.1

assertIsInstance(a, b)

isinstance(a, b)

3.2

assertNotIsInstance(a, b)

not isinstance(a, b)

3.2

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

四. unittest進階使用

方式一:類名("方法名")的集合

testsuite:

  addTest()  新增一個測試用例

  addTests([..])  新增多個測試用例,addTests的引數是一個測試用例的集合

  注意:addTests中執行用例的順序是按新增的先後順序進行的,如果這樣新增用例可能造成斷言失敗:

  suite.addTests([TestFileOperate("test_00_read_all"),
                TestFileOperate("test_01_write_data"),
                TestFileOperate("test_03_read_all"),
                TestFileOperate("test_02_add_data")])

  正確的做法是:

  suite.addTests([TestFileOperate("test_00_read_all"),
                TestFileOperate("test_01_write_data"),
                TestFileOperate("test_02_add_data"),
                TestFileOperate("test_03_read_all")])

 

s = unittest.TestSuite()

s.addTest(testStudent("test_do_homework"))

runner = unittest.TextTestRunner()

runner.run(s)

例項1:addTest的使用

在My_UnitTest包下建立一個main.py

技術分享圖片

main.py中的程式碼如下:

import unittest
from revise.My_UnitTest.Test_Myclass import Test_MyClass

#例項化測試套件物件
s = unittest.TestSuite()
#呼叫addTest來載入測試用例——addTest(類名("用例函式名稱"))——新增一個測試用例
s.addTest(Test_MyClass("test_add"))
s.addTest(Test_MyClass("test_minus"))

#使用TextTestRunner來執行測試用例
#例項化
runner = unittest.TextTestRunner()
#用run方法就是用來執行測試用例的
runner.run(s)

執行結果,控制檯輸出如下:

D:\Program\python34\python.exe D:/python_workshop/python6/revise/My_UnitTest/main.py
..
----------------------------------------------------------------------
Ran 2 tests in 0.001s

OK

例項2:addTests的使用

import unittest
from revise.My_UnitTest.Test_Myclass import Test_MyClass

#例項化測試套件物件
s = unittest.TestSuite()
#載入多個測試用例——引數為列表——列表當中為測試用例
s.addTests([Test_MyClass("test_add"), Test_MyClass("test_minus")])
#使用TextTestRunner來執行測試用例 #例項化 runner = unittest.TextTestRunner() #用run方法就是用來執行測試用例的 runner.run(s)

執行結果:

D:\Program\python34\python.exe D:/python_workshop/python6/revise/My_UnitTest/main.py
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

輸出測試報告-text-到檔案

#建立一個檔案,以寫的方式開啟

fs = open("test_result.txt", "w")

runner = unittest.TextTestRunner(fs)

runner.run(s)

例項3:輸出測試報告到檔案

import unittest
from revise.My_UnitTest.Test_Myclass import Test_MyClass

#例項化測試套件物件
s = unittest.TestSuite()
#載入多個測試用例——引數為列表——列表當中為測試用例
s.addTests([Test_MyClass("test_add"), Test_MyClass("test_minus")])

#使用TextTestRunner來執行測試用例
#開啟一個檔案
fs = open("test_run_result.txt", "w")
#例項化
runner = unittest.TextTestRunner(fs)
#用run方法就是用來執行測試用例的
runner.run(s)

執行結果,控制檯沒有輸出,發現當前目錄下多了一個文字檔案test_run_result.txt

技術分享圖片

控制檯輸出的資訊寫到文字檔案中了

技術分享圖片

方式二:unittest.TestLoader.discover方法匹配目錄下的用例

假如現在目錄下存在兩個測試用例,Test_Myclass.py和Test)_Myclass2.py,如果用addTests的方法新增用例到測試套件,未免有點麻煩,這時候需要使用TestLoader()這個類

技術分享圖片

程式碼如下:

import unittest, os

#例項化測試套件物件
s = unittest.TestSuite()
#1.例項化TestLoader物件  2.使用discover去找到一個目錄下的所有測試用例
loader = unittest.TestLoader()
#3.使用addTests將找到的測試用例放在測試套件下
s.addTests(loader.discover(os.getcwd()))
#執行 runner = unittest.TextTestRunner() runner.run(s)

執行結果:

....
----------------------------------------------------------------------
Ran 4 tests in 0.000s

OK

注意:原始碼中discover方法, start_dir是要尋找的目錄路徑,pattern是查詢條件,即在指定的目錄下查詢以"test"開頭的測試用例(事實上,這個查詢是不區分大小寫的,即Test開頭的也能找到)

技術分享圖片

方式三:unittest.TestLoader.loadTestsFromModule匹配模組中的測試用例

ps:TetLoader類、TestSuite類需要先例項化再使用

五. 美化測試報告—html

python有提供第三方庫支援輸出的測試報告為html樣式

庫名:HtmlTestRunner

 

匯入:from HtmlTestRunnerNew import HTMLTestRunner

 

使用方式:

s = unittest.TestSuite()

s.addTests(測試用例)

fp = open(dir_config.htmlreport_dir + "/API_autoTest_{0}.html".format(curTime), "wb")

runner = HTMLTestRunnerNew.HTMLTestRunner(

      stream = fp,

      title = "QCD介面測試報告",

      description = "QCD介面測試報告",

      tester = "xiaozhai"

      )

runner.run(s)

 

程式碼修改如下:

import unittest, os, time
from HTMLTestRunnerNew import HTMLTestRunner

#例項化測試套件物件
s = unittest.TestSuite()
#1.例項化TestLoader物件  2.使用discover去找到一個目錄下的所有測試用例
loader = unittest.TestLoader()
#3.使用addTests將找到的測試用例放在測試套件下
s.addTests(loader.discover(os.getcwd()))

#獲取當前時間
curTime = time.strftime("%Y-%m-%d_%H-%M-%S")
#在當前目錄下建立一個html檔案
fp = open(os.getcwd() + "/autoTest_report_{0}.html".format(curTime), "wb")

#執行測試用例,生成測試報告
runner = HTMLTestRunner(
        stream=fp,
        title="單元測試報告",
        description="Math類的單元測試報告",
        tester="xiaozhai"
)
runner.run(s)

執行效果:

D:\Program\python34\python.exe D:/python_workshop/python6/revise/My_UnitTest/main.py
ok test_add (Test_Myclass.Test_MyClass)
ok test_minus (Test_Myclass.Test_MyClass)
ok test_add (Test_Myclass2.Test_MyClass2)
ok test_minus (Test_Myclass2.Test_MyClass2)

Time Elapsed: 0:00:00.001000
Sun Jul  8 18:17:18 2018 - Start Test:test_add (Test_Myclass.Test_MyClass)
Sun Jul  8 18:17:18 2018 - Start Test:test_minus (Test_Myclass.Test_MyClass)
Sun Jul  8 18:17:18 2018 - Start Test:test_add (Test_Myclass2.Test_MyClass2)
Sun Jul  8 18:17:18 2018 - Start Test:test_minus (Test_Myclass2.Test_MyClass2)

技術分享圖片

技術分享圖片