Unittest是python單元測試框架,是受到 JUnit 的啟發,與其他語言中的主流單元測試框架有着相似的風格。其支持測試自動化,配置共享和關機代碼測試。支持將測試樣例聚合到測試集中,並將測試與報告框架獨立。它不僅適用於單元測試,還在自動化測試領域占有一席之地。借助它組織執行測試用例,使用它提供的豐富的斷言方法進行測試結果的比對,並結合HTMLTestRunner生成測試報告完成整個自動化測試流程。
二.簡單使用示例
創建被測類calc.py
class count: def __init__(self, a, b): self.a = a self.b = b #計算加法 def add(self): return self.a + self.b #計算減法 def Subtraction(self): return self.a - self.b
通過unittest單元測試框架編寫單元測試用例 test.py
from calc import count import unittest class CountTest(unittest.TestCase): def setUp(self) -> None: print("in case setUP") def tearDown(self) -> None: print("in case tearDow") def test_add(self): c = count(4, 5) self.assertEqual(c.add(), 9) def test_subtraction(self): f = count(9, 8) self.assertEqual(f.Subtraction(), 1) if __name__ == '__main__': CountTest.main()
說明:
-
首先引入unittest模塊,創建testcount類繼承unitest的testcase類。
-
setUp():用於測試用例執行前的初始化工作,與tearDown()相呼應,用於執行后的善后工作。
-
test_add中調用count類並傳入要計算的數,通過調用add()方法得到兩數相加的返回值,這里不再使用繁瑣的異常處理,而是調用unitest框架所提供的assertEqual()對add()的返回值進行斷言判斷兩者是否相等()。assertEqual()方法是由testcase類繼承而來的。
-
main():unittest提供了全局的main()方法,使用它可以方便的將一個單元測試模塊變成可以直接運行的測試腳本。main()方法使用Testloader類來搜索所有包含在該模塊中以“test”命名開頭的測試方法。
-
name:作為模塊的內置屬性,簡單地說就是.py文件的調用方式。.py文件有兩種使用方式作為模塊調用和直接使用,如果它等於“main”就表示是直接使用
三.unittest模塊說明
-
TestCase:一個TestCase的實例就是一個測試用例,是一個完整的測試流程,包括測試前准備環境的搭建(setUp),實現測試過程的代碼(run),測試后環境的還原(tearDown).
-
Test Suite:把多個測試用例集合在一起來執行。可以通過addTest加載TestCase到Test Suite(測試套件)中,從而返回一個TestSuite實例。
-
Test Runner:測試的執行,通過TextTestRunner類提供的run()方法來執行Test Suite/TestCase。Test Runner可以使用圖形界面,文本界面,或者返回一個特殊的值的方式來表示測試執行的結果。
-
Test Fixture:對一個測試用例環境的初始化和清除。通過覆蓋TestCase的setUp()和tearDown()方法來實現。tearDown()為下一個測試用例提供一個干凈的環境。
四.斷言方法
unittest框架的TestCase類提供的斷言方法用於測試結果的判斷
1.判斷第一個參數和第二個參數是否相等
-assertEqual(first,second,msg=None) # 如果不相等則測試失敗,msg為可選參數,用於定義測試失敗時打印的信息。 self.assertEqual(j.add(),15,msg="測試結果不等於15")
格式:-assertNotEqual(first,second,msg=None)則與之相反
2.判斷表達式是true(或false)
創建count.py用於判斷質數的
def is_prime(n): if n<=1: return False for i in range(2,n): if n % i = = 0: return False retun True
測試用例:調用is_prime()函數和unittest
self.assertTrue(is_prime(7),msg="is not prime")
3.判斷第一個參數是否在第二個參數中,簡言之也就是檢查第二個參數是否包含第一個參數。
-assertIn(first,second,msg=None)
-assertNotIn(first,second,msg=None)
測試用例(部分):
def test_case(self): a = "hello" b = "hello world" self.assertIn(a,b,msg="a is not in b")
4.判斷第一個參數和第二個參數是否為同一對象
-assertIs(first,second,msg=None)
-assertIsNot(first,second,msg=None)
5.判斷表達式是否為None對象
-assertIsNone(expr,msg=None)
-assertIsNotNone(expr,msg=None)
6.判斷obj是否為cls的一個實例格式:
-assertIsInstance(obj,cls,msg=None)
-assertNotIsInstance(obj,cls,msg=None)
五.測試用例的組織
1.初始化和清除
fixtures可以形象的看作是夾心餅干外層的兩片餅干,這兩片餅干就是setUp/tearDown,中間的心就是測試用例,除此以外,unittest還提供了更大范圍的fixtures,例如對於測試類和模塊的fixtures。
-
-
-
setUpModule/tearDownModule:在整個模塊的開始和結束時被執行。
-
setUpClass/tearDownClass: 在測試類的開始和結束時被執行。
-
setUp/tearDown:在測試用例的開始與結束時被執行 注意:setUpClass/tearDownClass的寫法稍有不同,首先通過@classmethod進行裝飾,其次方法的參數為cls,也可以是別的。每一個上面都要進行裝飾
-
-
2.創建測試套件
測試套件是用例的集合,在實際測試工作中,經常會聽說冒煙測試、全量測試、回歸測試等測試版本,每個版本所選取的測試用例也是不一樣,
如果測試用例數量比較多,可以將這些用例按照所測試的功能進行拆分,分散到不同的測試文件中 最后再創建用於執行所有測試用例的runtest.py文件。
方法1:可以通過addTest()加載TestCase到TestSuite中。用於少量的測試用例
from test import CountTest import unittest #創建套件 suite = unittest.TestSuite() #方法一:添加單個測試用例到套件之中 suite.addTest(CountTest('test_add')) suite.addTest(CountTest('test_subtraction')) runner = unittest.TextTestRunner() runner.run(suite)
方法2:同時添加多個測試用例到套件之中
from test import CountTest import unittest #創建套件 suite = unittest.TestSuite() #測試用例集合,放在列表中 case = [CountTest('test_add'), CountTest('test_subtraction')] #將測試用例集合添加到套件中 suite.addTests(case) #創建運行器 runner = unittest.TextTestRunner() #執行套件 runner.run(suite)
方法3:模糊匹配給套件指定需要執行的測試用例
使用TestLoader類提供的discover()方法來加載所有的測試用例。正常情況下,不需要創建這個類的實例,unittest提供了可以共享的defaultTestLoader類,可以使其子類和方法創建實例,discover()方法就是其中之一。
discover(start_dir,pattern='test*.py',top_level_dir=None)
start_dir:要測試的模塊名或測試用例的目錄
pattern='test.py':表示用例文件名的匹配原則,此處文件名以“test”開頭的“.py”類型的文件,“”表示任意多個字符。
top_level_dir=None:測試模塊的頂層目錄,如果沒有頂層目錄,默認為None.
#指定路徑 test_dir = './' #指定項目根目錄下,所有的以tes開頭的.py文件中的測試用例 discover = unittest.defaultTestLoader.discover(start_dir=test_dir, pattern='tes*.py') #創建運行器 runner = unittest.TextTestRunner() #執行套件 runner.run(discover)
方法4:添加整個類中的測試用例到套件中
#CountTest是一個測試用例類 from test import CountTest import unittest #創建套件 suite = unittest.TestSuite() #將CountTest類中所有用例添加到套件 suite.addTests(unittest.TestLoader().loadTestsFromTestCase(CountTest)) #創建運行器 runner = unittest.TextTestRunner() #執行套件 runner.run(suite)
3.用例執行的原則
unittest框架默認根據ASCII碼的順序加載測試用例,數字與字母的順序為:0-9,A-Z,a-z。所以TestAdd會優於TestBdd類被執行,test_aaa()方法會優於test_ccc被執行,因而它並沒有按照用例從上到下的順序執行。
對於測試目錄和測試文件來說,unittest框架同樣是按照這個規則來加載測試用例的。 如果按照指定的順序執行,可以通過TestSuite類的addTest()方法按照一定的順序加載。不能默認main()方法了。需要構造測試集,然后通過run()方法執行測試。
注意:discover()的加載測試用例的規則與main()方法相同,所以只能通過測試用例的命名來提高被執行的優先級。
4.執行多級的用例
discover()方法中的start_dir只能加載當前目錄下的.py文件,如果加載子目錄下的.py文件,需在每個子目錄下放一個init.py文件。
5.跳過測試和預期失敗
unittest提供了實現某些需求的裝飾器,在執行測試用例時每個裝飾前面加@符號。
-
@unittest.skip(reason):無條件的跳過裝飾的測試,說明跳過測試的原因
-
@unittest.skipIf(condition,reason):跳過裝飾的測試,如果條件為真。
-
@unittest.skipUnless(condition,reason):跳過裝飾的測試,除非條件為真。
-
@unittest.expectedFailure():測試標記為失敗,不管執行結果是否失敗,統一標記為失敗,但不會拋出錯誤信息。
-
@unittest.expectedFailure #如果斷言失敗,不計入執行case數目中
六.測試報告生成
1.HTMLTestRunner介紹
HTMLTestRunner是python標准庫unittest單元測試框架的一個拓展,它生成易於使用的HTML測試報告。HTMLTestRunner下載地址:http://tungwaiyip.info/software/HTMLTestRunner.html,在GitHub上也有一個人在這個基礎上做過改動的,可以自己去下載即可 下載下來是一個HTMLTestRunner.py文件,選中后單擊鼠標右鍵,在彈出的快捷菜單中選擇目標另存為,將它保存到本地。安裝方法是將其復制到python安裝目錄下即可。
windows:將下載的文件保存到...\python36\Lib目錄下
HTMLTestRunner.py文件是基於python2開發的,若要支持python3環境需要對其中的部分內容進行修改。目前可以在GitHub上找到可供python3使用已經修改過的版本
2.生成HTML測試報告
-
將HTMLTestRunner模塊用import導入進來
-
通過open()方法以二進制寫模式打開當前目錄下的result.html,如果沒有,則自動創建該文件。
-
調用HTMLTestRunner模塊下的HTMLTestRunner類,stream指定測試報告文件,title用於定義測試報告的標題, description用於定義測試報告的副標題。
-
最后,通過HTMLTestRunner的run()方法來運行測試套件中所組裝的測試用例。
-
通過close()關閉測試報告文件。
from test import CountTest import unittest import os from HTMLTestRunner import HTMLTestRunner import time report_title = '冒煙測試' report_desc = '本次測試描述' report_path = './report/' #創建測試報告存放的位置和名字 report_file = report_path + 'report8.html' #判斷項目中是否有report_path所對應的目錄,如果沒有則新建。有了直接存放結果即可 if not os.path.exists(report_path): os.mkdir(report_path) else: pass #創建測試套件 suite = unittest.TestSuite() #執行套件內容,將結果寫入 open(report_file,'wb') as report: #添加用例到套件 suite.addTests(unittest.TestLoader().loadTestsFromTestCase(CountTest)) #生成運行器 runner = HTMLTestRunner(stream=report, title=report_title, description=report_desc) #執行套件 runner.run(suite) # 關閉文件流,不關的話生成的報告是空的(看) # report.close()
3.測試報告名稱
在每次運行測試之前,都要手動修改報告的名稱,如果忘記修改,就會把之前的報告覆蓋,為了使每次生成的報告名稱都不重復並且有意義,可以在報告名稱中加入當前時間,這樣生成的報告既不會重疊,又能更清晰的知道報告的生成時間。
time.time():獲取當前時間戳 比如:1601886309.2652433
time.ctime():當前時間的字符串形式 比如:'Mon Oct 5 16:25:31 2020'
time.localtime():當前時間的struct_time形式 比如:time.struct_time(tm_year=2020, tm_mon=10, tm_mday=5, tm_hour=16, tm_min=25......等等。
time.strftime("%Y-%m-%d %H:%M:%S"):用來獲得當前時間,可以將時間格式化為字符串。比如:'2020-10-05 16:27:17'
方法:通過時間操作的方法以指定的格式獲取當前時間,將當前時間的字符串賦值給rtime變量,將rtime通過字符串格式化操作拼接到生成的測試報告的文件名中,再次運行測試用例,即可生成測試報告文件名。
import unittest, os, time from webDriver.driver_factory import DriverFactory from common.HTMLTestRunner_cn import HTMLTestRunner report_title = '冒煙測試' report_desc = '本次測試描述' report_path = './report/' rtime = time.strftime("%Y%m%d%H%M%S") #創建測試報告存放的位置和名字 report_file = report_path + f'report{rtime}.html' # #判斷項目中是否有report_path所對應的目錄,如果沒有則新建。有了直接存放結果即可 if not os.path.exists(report_path): os.mkdir(report_path) else: pass #添加用例到套件,采用模糊匹配,在test_case目錄下查找 test_dir = './test_case' #指定項目根目錄下,所有的以tes開頭的.py文件中的測試用例 discover = unittest.defaultTestLoader.discover(start_dir=test_dir, pattern='test_*.py') #執行套件內容,將結果寫入 with open(report_file, 'wb') as report: #生成運行器 runner = HTMLTestRunner(stream=report, title=report_title, description=report_desc) #執行套件 runner.run(discover) #測試套件執行結束關閉瀏覽器 DriverFactory.driver.quit()