Unittest
Unittest是python的一個單元測試框架,但是它不僅適用於單元測試,還適用自動化測試用例的開發與執行。我們可以很方便的使用它組織執行測試用例,使用它提供的豐富的斷言方法進行測試結果比對,並最終結合HTMLTestRunner生成測試報告完成整個自動化測試流程。

簡單使用示例
創建被測類calculator.py
class count:
def _init_(self,a,b)
self.a = int(a)
self.b = int(b)
#計算加法
def add(self):
return self.a + self.b
通過unittest單元測試框架編寫單元測試用例 test.py
from calculator import count
import unitest
class TestCount(unitest.TestCase):
def setUp(self):
print("test start")
def test_add(self):
j = count(2,3)
self.assertEqual(j.add(),5)
def tearDown(self):
print("test end")
if _name_ == '_main_':
unittest.main()
說明:
- 首先引入unitest模塊,創建testcount類繼承unitest的testcase類。
- setUp():用於測試用例執行前的初始化工作,與tearDown()相呼應,用於執行后的善后工作。
- test_add中調用count類並傳入要計算的數,通過調用add()方法得到兩數相加的返回值,這里不再使用繁瑣的異常處理,而是調用unitest框架所提供的assertEqual()對add()的返回值進行斷言判斷兩者是否相等。assertEqual()方法是由testcase類繼承而來的。
- main():unitest提供了全局的main()方法,使用它可以方便的將一個單元測試模塊變成可以直接運行的測試腳本。main()方法使用Testloader類來搜索所有包含在該模塊中以“test”命名開頭的測試方法。
- name:作為模塊的內置屬性,簡單地說就是.py文件的調用方式。.py文件有兩種使用方式作為模塊調用和直接使用,如果它等於“main”就表示是直接使用
重要概念
- 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()為下一個測試用例提供一個干凈的環境。
from calculator import count
import unitest
class TestCount(unitest.TestCase):
def setUp(self):
print("test start")
def test_add(self):
j = count(2,3)
self.assertEqual(j.add(),5)
def test_add2(self):
j = count(7,8)
self.assertEqual(j.add(),15)
def tearDown(self):
print("test end")
if _name_ == '_main_':
unittest.main()
#構建測試集
suite = unittest.TestSuite()
suite.addTest(TestCount("test_add2"))
#執行測試
runner = unittest.TextTestRunner()
runner.run(suite)
本例只執行第二個測試用例,
- 調用unittest框架的TestSuite()來創建測試套件。
- 通過它所提供的addTest()方法來添加測試用例test_add2。
- 調用unitest框架的TextTestRunner().
- 通過它下面的run()方法來運行suite所組裝的測試用例。
斷言方法
unittest框架的TestCase類提供的斷言方法用於測試結果的判斷
判斷第一個參數和第二個參數是否相等
-assertEqual(first,second,msg=None)
# 如果不相等則測試失敗,msg為可選參數,用於定義測試失敗時打印的信息。
self.assertEqual(j.add(),15,msg="測試結果不等於15")
格式:-assertNotEqual(first,second,msg=None)則與之相反
判斷表達式是true(或false)
-assertTrue(expr,msg=None)
-assertFalse(expr,msg=None)
創建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")
判斷第一個參數是否在第二個參數中,就是第二個參數是否包含第一個參數。
-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")
判斷第一個參數和第二個參數是否為同一對象
-assertIs(first,second,msg=None)
-assertIsNot(first,second,msg=None)
判斷表達式是否為None對象
-assertIsNone(expr,msg=None)
-assertIsNotNone(expr,msg=None)
判斷obj是否為cls的一個實例格式:
-assertIsInstance(obj,cls,msg=None)
-assertNotIsInstance(obj,cls,msg=None)
深入
組織單元測試用例
- 方法1.setUp()和setDown方法分別作用於每個測試用例的開始和結束
- 方法2.如果每個類中的setUp()和setDown方法所做的事情是一樣的,那么可以封裝成一個自己的測試類
discover更多測試用例
如果單元測試用例達到成百上千個,可以將這些用例按照所測試的功能進行拆分,分散到不同的測試文件中
最后再創建用於執行所有測試用例的runtest.py文件。
- 方法1:可以通過addTest()加載TestCase到TestSuite中。用於少量的測試用例
- 方法2:使用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.
用例執行的原則
unittest框架默認根據ASCII碼的順序加載測試用例,數字與字母的順序為:0-9,A-Z,a-z。所以TestAdd會優於TestBdd類被執行,test_aaa()方法會優於test_ccc被執行,因而它並沒有按照用例從上到下的順序執行。
對於測試目錄和測試文件來說,unittest框架同樣是按照這個規則來加載測試用例的。
如果按照指定的順序執行,可以通過TestSuite類的addTest()方法按照一定的順序加載。不能默認main()方法了。需要構造測試集,然后通過run()方法執行測試。
注意:discover()的加載測試用例的規則與main()方法相同,所以只能通過測試用例的命名來提高被執行的優先級。
執行多級的用例
discover()方法中的start_dir只能加載當前目錄下的.py文件,如果加載子目錄下的.py文件,需在每個子目錄下放一個_init_.py文件。
跳過測試和預期失敗
unittest提供了實現某些需求的裝飾器,在執行測試用例時每個裝飾前面加@符號。
- unittest.skip(reason):無條件的跳過裝飾的測試,說明跳過測試的原因
- unittest.skipIf(condition,reason):跳過裝飾的測試,如果條件為真。
- unittest.skipUnless(condition,reason):跳過裝飾的測試,除非條件為真。
- unittest.expectedFailure():測試標記為失敗,不管執行結果是否失敗,統一標記為失敗,但不會拋出錯誤信息。
fixtures
fixtures可以形象的把他看作是夾心餅干外層的兩片餅干,這兩片餅干就是setUp/tearDown,中間的心就是測試用例,除此以外,unittest還提供了更大范圍的fixtures,例如對於測試類和模塊的fixtures。
- setUpModule/tearDownModule:在整個模塊的開始和結束時被執行。
- setUpClass/tearDownClass: 在測試類的開始和結束時被執行。
- setUp/tearDown:在測試用例的開始與結束時被執行
注意:setUpClass/tearDownClass的寫法稍有不同,首先通過@classmethod進行裝飾,其次方法的參數為cls,也可以是別的。每一個上面都要進行裝飾
高級應用
HTMLTestRunner
HTMLTestRunner是python標准庫unittest單元測試框架的一個拓展,它生成易於使用的HTML測試報告。
HTMLTestRunner下載地址:http://tungwaiyip.info/software/HTMLTestRunner.html
這個拓展只有一個HTMLTestRunner.py文件,選中后單擊鼠標右鍵,在彈出的快捷菜單中選擇目標另存為,將它保存到本地。安裝方法是將其復制到python安裝目錄下即可。
- windows:將下載的文件保存到...\python35\Lib目錄下
- Ubuntu:以root身份將HTMLTestRunner.py文件復制到/usr/local/python3.4/dist-packages/目錄下。
HTMLTestRunner.py文件是基於python2開發的,若要支持python3環境需要對其中的部分內容進行修改。
生成HTML測試報告
- 將HTMLTestRunner模塊用import導入進來
- 通過open()方法以二進制寫模式打開當前目錄下的result.html,如果沒有,則自動創建該文件。
- 調用HTMLTestRunner模塊下的HTMLTestRunner類,stream指定測試報告文件,title用於定義測試報告的標題,
description用於定義測試報告的副標題。 - 最后,通過HTMLTestRunner的run()方法來運行測試套件中所組裝的測試用例。
- 通過close()關閉測試報告文件。
更易讀的測試報告
方法:加注釋並用一種方法讀取
在類和方法的下方,通過三引號(""" """或''' ''')來添加docstring類型的注釋,這類注釋在平時調用的時候
不顯示,可以通過help()方法來查看類或方法的這種注釋。
HTMLTestRunner可以讀取docstring類型的注釋,所以只需給測試類或方法添加這種類型的注釋即可。
測試報告名稱建議
在每次運行測試之前,都要手動修改報告的名稱,如果忘記修改,就會把之前的報告覆蓋,為了使每次生成的報告名稱都不重復並且有意義,最好的方法是在報告名稱中加入當前時間,這樣生成的報告既不會重疊,又能更清晰的知道報告的生成時間。
- time.time():獲取當前時間戳 比如:1445694559.2290168
- time.ctime():當前時間的字符串形式 比如:'sat oct 24 21:49:29 2015'
- time.localtime():當前時間的struct_time形式 比如:tm_year=2015,tm_mon=10,tm_mday=24,..........等等。
- time.strftime("%Y_%m_%d %H:%M:%s"):用來獲得當前時間,可以將時間格式化為字符串。比如:'2015_10_24 21:50:15'
方法:通過時間操作的方法以指定的格式獲取當前時間,將當前時間的字符串賦值給now變量,將now通過加號(+)拼接到生成的測試報告的文件名中,再次運行測試用例,生成測試報告文件名。
項目集成測試報告
目前HTMLTestRunner只是針對單個測試文件生成測試報告,若要使其作用於整個測試項目,
要將它集成到runtest.py文件中,對其文件進行修改。
認識Page Object
Page Object設計模式的優點
- 減少代碼的重復性
- 提高測試用例的可讀性
- 提高測試用例的可維護性,特別是針對UI頻繁變化的項目
為web頁面編寫測試時,需要操作該web頁面上的元素,如果在測試代碼中直接操作HTML元素,那么你的代碼是極其脆弱的,因為UI經常變動。我們可以將一個page對象封裝成一個HTML頁面,然后通過提供的應用程序特定的API來操作頁面元素,而不是在HTML中四處搜尋。
page對象應當將在GUI控件上所有查詢和操作數據的行為封裝為方法,即使改變具體的控件,page對象的接口也不應當發生變化。
“頁面”對象不僅是針對每個頁面建立一個這樣的對象,對由重要意義的元素也可以獨立為一個page對象。
Page Object實例
- 創建page類
- 創建LoginPage類
- 創建test_user_login()函數
- 創建main()函數
