一,前言
1,單元測試
軟件測試一般按階段划分為:單元測試,集成測試,系統測試。單元測試(unit testing)是指對軟件中的最小可測試單元進行檢查和驗證。 單元測試中單元的含義,單元就是人為規定的最小的被測功能模塊,如C語言中單元指一個函數,Java里單元指一個類,圖形化的軟件中可以指一個窗口或一個菜單等。在實際項目中,單元測試往往由開發人員完成。
2,單元測試框架
-
單元測試其實就是構造數據使用一段代碼去測試另一段代碼,理論上來說,不使用單元測試框架也能進行單元測試。但如果用於單元測試的代碼(即測試用例)增多,在沒有測試框架的情況下會變得擁擠、不可管理,這個時候引入測試框架就變得尤為重要。
-
單元測試框架提供了一種統一的編程模型,可以將測試定義為一些簡單的類,這些類中的方法可以調用希望測試的應用程序代碼。利用單元測試框架,可以很輕松地插入、設置和分解有關測試的功能,可以直觀方便地管理測試用例。
-
主流的單元測試框架,如Java的Junit、TestNg,python的Unittest、Pyunit、Pytest,通用的自動化測試框架Robot Framework等。
3,單元測試框架作用
-
提供用例組織與執行
-
提供豐富的斷言方法
-
提供豐富的日志與測試結果
二,Unittest 測試框架
1,Unittest 簡介
Unittest是Python自帶的單元測試框架,不僅適用於單元測試,還可用於Web、Appium、接口自動化測試用例的開發與執行。該測試框架可組織執行測試用例,並且提供豐富的斷言方法,判斷測試用例是否通過,並最終生成測試結果。
Unittest官方文檔:https://docs.python.org/3/library/unittest.html
2,Unittest 核心要素
-
TestCase:即測試用例,Unittest提供testCase類來編寫測試用例,一個TestCase的實例就是一個測試用例。一條測試用例就是一個完整的測試流程,包括測試前准備環境的搭建(setUp),執行測試代碼(run),以及測試后環境的還原(tearDown),通過運行一條測試用例,可以對某一個問題進行驗證。
-
Fixture:即測試固件,用於測試用例環境的搭建和銷毀。在測試步驟執行前需要為該測試用例准備環境(SetUp),如啟動app或打開瀏覽器,測試步驟執行后需要恢復環境 (TearDown),如關閉app或瀏覽器,這時候就需要用到Fixture,使代碼更簡潔。
-
TestSuite:即測試套件,把需要執行的測試用例集合在一起就是TestSuite。使用TestLoader來加載TestCase到TestSuite中。
-
TextTestRunner:即測試執行器,用於執行測試用例。該模塊中提供run方法執行TestSuite中的測試用例,並返回測試用例的執行結果,如運行的用例總數、用例通過數、用例失敗數。
-
report:即測試報告。unittest框架沒有自帶的用於生成測試報告的模塊或接口,需要使用第三方的擴展模塊HTMLTestRunner。
3,Unittest 斷言
斷言在自動化測試腳本中是很重要的內容,只有設置正確合適的斷言才能獲取正確的測試結果。Unittest框架提供了自己的斷言方法,如下:
斷言方法
|
判斷內容
|
---|---|
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) |
注意:
-
如果斷言成功則該條測試用例通過,斷言失敗則該條測試用例執行失敗,且會拋出AssertionError錯誤。
-
以上提供的斷言方法中,都有一個msg參數,默認為None。如果msg參數有對應的值,則斷言失敗后該msg的值會作為失敗信息返回,如 assertEqual(a, b, msg="a與b不相等!") 。
三,Unittest 框架使用方法
1,測試需求
測試對象:構造一個類Math,其中包含整數的加、減法運算。
calculator.py
class Math():
def __init__(self, a, b):
self.a = int(a)
self.b = int(b)
def sum(self):
'''和'''
return self.a + self.b
def sub(self):
'''差'''
return self.a - self.b
測試需求:對Math類進行單元測試。接下來針對這個測試需求,使用unittest框架編寫測試用例。
項目目錄結構:后面的例子中,項目結構如下所示。
2,編寫TestCase(測試用例)
在Unittest框架下創建測試用例,步驟如下:
-
1),導入unittest模塊。
-
2),創建測試類。測試類的命名不做要求,但需要繼承unittest.TestCase類。
-
3),添加setUp()、tearDown()函數,即測試固件。當然還有setUpClass()、tearDownClass() 函數,區別后面會有介紹。
-
4),定義測試方法,即測試用例。測試方法名稱必須以test開頭,否則測試時該方法將不會被執行。測試方法里需要添加斷言。
-
5),調試執行測試用例。執行當前模塊的測試用例時,調用unittest.main()方法,該方法會搜索該模塊下所有以test開頭的測試用例方法,並執行。其他方法后面介紹。
針對測試需求,編寫測試用例。目錄結構如下:
test_sum.py
import unittest
from calculator import Math
class SumTest(unittest.TestCase):
'''測試Math類中的sum函數'''
def setUp(self):
print("開始執行測試用例{}...".format(self))
def test_sum01(self):
m = Math(3, 4)
self.assertEqual(m.sum(), 7)
def test_sum02(self):
m = Math(2, 8)
self.assertEqual(m.sum(), 11)
def tearDown(self):
print("測試用例{}執行結束...".format(self))
if __name__ == '__main__':
unittest.main()
運行結果:
.F
======================================================================
FAIL: test_sum02 (__main__.SumTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:/Users/xiaoqq/Desktop/test_project/demo/testSum.py", line 15, in test_sum02
self.assertEqual(m.sum(), 11)
AssertionError: 10 != 11
----------------------------------------------------------------------
Ran 2 tests in 0.001s
FAILED (failures=1)
開始執行測試用例test_sum01 (__main__.SumTest)...
測試用例test_sum01 (__main__.SumTest)執行結束...
開始執行測試用例test_sum02 (__main__.SumTest)...
測試用例test_sum02 (__main__.SumTest)執行結束...
Process finished with exit code 0
結果顯示:
-
test_sum01通過,test_sum02失敗。點"."表示通過,"F"表示失敗。
-
測試類中每個測試方法(即測試用例)執行前,都先執行setUp()方法,每個測試方法執行完畢后都要執行tearDown()方法。
-
斷言失敗會返回一個AssertionError。
3,在測試用例中添加Fixture(測試夾具)
3.1,測試夾具Fixture的作用對象
在Unittest框架下的測試用例中,使用Fixture有兩種方法,作用於兩個范圍:
-
setUp()、tearDown(),作用於測試方法。即測試類下的每個測試方法執行前先運行setUp(),每個測試方法執行完畢后都要執行tearDown()方法,如testSum.py示例。
-
setUpClass()、tearDownClass(),作用於測試類。即只在整個測試類執行開始時運行setUpClass(),測試類下所有測試方法執行完后運行tearDownClass()。
3.2,setUpClass()、tearDownClass() 舉例
test_sum.py修改如下,運行
class SumTest(unittest.TestCase):
'''測試Math類中的sum函數'''
@classmethod
def setUpClass(cls):
print("開始執行測試用例{}...".format(cls))
def test_sum01(self):
m = Math(3, 4)
self.assertEqual(m.sum(), 7)
def test_sum02(self):
m = Math(2, 8)
self.assertEqual(m.sum(), 11)
@classmethod
def tearDownClass(cls):
print("測試用例{}執行結束...".format(cls))
if __name__ == '__main__':
unittest.main()
運行結果:
開始執行測試用例<class '__main__.SumTest'>...
測試用例<class '__main__.SumTest'>執行結束...
.F
結果顯示,setUpClass()、tearDownClass() 都只運行了一次。注意,這里需要使用裝飾器@classmethod
3.3,注意
-
在測試用例中,setUp() 或 setUpClass() 做初始化的工作,不是必須有這個函數。同樣tearDown() 和 tearDownClass() 只做清理的工作,在測試類中不是必須要有。
-
需要測試的Math類,代碼比較簡單,沒有真正需要用到測試夾具的地方,因此這只是個用法演示。
-
實際自動化過程中,如Web端UI自動化,一般會將創建瀏覽器實例放在setUp() ,用例執行完后需要關閉瀏覽器,將關閉瀏覽器操作放在tearDown()方法里。示例如下:
import unittest
from selenium import webdriver
class BaiduTest(unittest.TestCase):
def setUp(self):
'''打開瀏覽器,進入百度頁面'''
self.driver = webdriver.Chrome()
self.driver.maximize_window()
self.driver.get('https://www.baidu.com')
def test_01(self):
print("操作步驟")
def tearDown(self):
'''關閉瀏覽器'''
self.driver.quit()
4,將測試用例添加至TestSuite(測試套件)
在testSum.py模塊中,使用了unittest.main()方法執行當前模塊里的測試用例。除此之外,Unittest還可以通過測試套件構造測試用例集,再執行測試用例。構造TestSuite常用的方法如下:
4.1,方法一:加載測試用例
-
1),先通過unittest.TestSuite() 創建測試套件實例對象。
-
2),再通過addTest() 往測試套件里添加單個測試用例,或通過addTests([...]) 添加多個測試用例(列表中為用例方法名)。
-
3),執行測試套件里的測試用例
run.py示例:
import unittest
# 導入測試用例模塊
from testcase.test_sum import TestDemo
# 第一步:創建TestSuite實例
suite = unittest.TestSuite()
# 第二步:將測試用例添加至TestSuite
# 方式1,添加單條測試用例
suite.addTest(TestDemo('test_sum01')) # addTest()里參數格式為:測試類('測試方法')
suite.addTest(TestDemo('test_sum02'))
# 方式2,添加多條測試用例
suite.addTests([TestDemo('test_sum01'), TestDemo('test_sum02')])
4.2,方法二:加載測試用例類
-
1),先通過unittest.TestSuite() 創建測試套件實例對象。
-
2),再通過unittest.TestLoader()創建加載對象,加載測試用例類
run.py示例:
import unittest
# 導入測試用例模塊
from testcase.test_sum import TestDemo
# 創建TestSuite實例
suite = unittest.TestSuite()
# 創建一個加載對象
loader = unittest.TestLoader()
suite.addTest(loader.loadTestsFromTestCase(TestDemo))
4.3,方法三:加載指定路徑里的測試用例
-
1),通過unittest.defaultTestLoader.discover()將指定路徑的測試用例加載至測試用例集。注意:這里不需要創建unittest.TestSuite對象
-
2),如下代碼所示,test_dir為指定路徑。pattern=test_*.py 表示加載以test_開頭的模塊中的測試用例,指定運行某模塊時pattern參數指定全名即可,如:pattern='test_sum.py'。路徑跟pattern參數都可以自定義。
run.py示例
import unittest
test_dir = './testcase'
suite = unittest.defaultTestLoader.discover(test_dir, pattern='test_*.py')
編寫測試項目時,推薦使用方法三。當然還有其他方法,不多做介紹。
5,使用TextTestRunner執行測試用例
unittest框架執行測試用例之前,需先創建TextTestRunner實例,再調用該實例的run()方法。
# 創建TextTestRunner實例
runner = unittest.TextTestRunner()
# 使用run()方法運行測試套件(即運行測試套件中的所有用例)
runner.run(suite)
run.py修改成如下示例:
import unittest
test_dir = './testcase'
suite = unittest.defaultTestLoader.discover(test_dir, pattern='test_*.py')
runner = unittest.TextTestRunner()
runner.run(suite)
運行run.py,結果如下:
.F
======================================================================
FAIL: test_sum02 (test_sum.TestDemo)
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\Users\xiaoqq\Desktop\test_project\demo\test_sum.py", line 15, in test_sum02
self.assertEqual(m.sum(), 11)
AssertionError: 10 != 11
----------------------------------------------------------------------
Ran 2 tests in 0.000s
FAILED (failures=1)
開始執行測試用例test_sum01 (test_sum.TestDemo)...
測試用例test_sum01 (test_sum.TestDemo)執行結束...
開始執行測試用例test_sum02 (test_sum.TestDemo)...
測試用例test_sum02 (test_sum.TestDemo)執行結束...
Process finished with exit code 0
四,輸出測試報告
unittest框架執行測試用例完成后會在控制台輸出如上的結果,但實際測試過程中,我們需要輸出測試報告,這個時候我們需要使用第三方模塊。
1,HTMLTestRunner
HTMLTestRunner模塊可以直接生成html格式的報告
-
下載后需要修改:
- 94行引入的名稱要改,從 import StringIO修改成 import io
- 539行 self.outputBuffer = StringIO.StringIO()修改成self.outputBuffer=io.StringIO()
- 631行 print >>sys.stderr, '\nTime Elapsed: %s' % (self.stopTime-self.startTime)修改成print (sys.stderr, '\nTime Elapsed: %s' %(self.stopTime-self.startTime))
- 642行,if not rmap.has_key(cls): 修改成 if not cls in rmap:
- 766行的uo = o.decode('latin-1'),修改成 uo=o
- 772行,把 ue = e.decode('latin-1') 直接改成 ue = e
-
存放路徑:將修改完成的模塊存放在Python路徑下Lib目錄里即可。
run.py示例代碼如下:
# -*- coding:utf-8 -*-
# @author: 給你一頁白紙
import time
import unittest
import HTMLTestRunner
# 獲取當前時間並指定時間格式,用於測試報告命名
now = time.strftime("%Y-%m-%d_%H_%M_%S")
# 測試報告存儲路徑
report_dir = './report/'
# 創建報告文件,並以寫的形式打開文件,用於寫入報告內容
fp = open(report_dir + now + "_report.html", 'wb')
# 初始化一個HTMLTestRunner實例對象,用來生成報告
runner = HTMLTestRunner.HTMLTestRunner(stream=fp,
title="App自動化測試報告",
description="測試用例情況")
# 定義測試用例路徑
test_dir='./testcase'
# 加載測試用例
suite = unittest.defaultTestLoader.discover(test_dir, pattern='test_*.py')
# 執行測試用例
runner.run(suite)
fp.close()
運行run.py,會看到report中生成了html文件,即為測試報告
瀏覽器打開該文件,內容如下:
從報告內容中看出HTMLTestRunner.HTMLTestRunner()方法中參數所對應的內容,可以根據項目的實際需要指定參數內容。
2,美化版測試報告
在HTMLTestRunner基礎上美化過的報告
-
下載地址: 鏈接:https://pan.baidu.com/s/1Wd_FXJBu3ATgmCQHkzbGag,提取碼:f6uq
-
放置在Python安裝路徑的Lib文件夾里
run.py示例代碼如下:
# -*- coding:utf-8 -*-
# @author: 給你一頁白紙
import time
import unittest
import BSTestRunner
# 獲取當前時間並指定時間格式,用於測試報告命名
now = time.strftime("%Y-%m-%d_%H_%M_%S")
# 測試報告存儲路徑
report_dir = './report/'
# 創建報告文件
fp = open(report_dir + now + "_report.html", 'wb')
# 初始化一個HTMLTestRunner實例對象,用來生成報告
runner = BSTestRunner.BSTestRunner(stream=fp,
title="App自動化測試報告",
description="測試用例情況")
# 定義測試用例路徑
test_dir='./testcase'
# 加載測試用例
suite = unittest.defaultTestLoader.discover(test_dir, pattern='test_*.py')
# 執行測試用例
runner.run(suite)
fp.close()
生成報告樣式如下:
兩種報告模板可根據自己喜好任意選擇。