Selenium自動化測試-unittest單元測試框架使用
一、什麼是unittest
當我們寫的用例越來越多時,我們就需要考慮用例編寫的規範與組織,以便於後期的維護,而unittest正是這樣一款工具。我們這裡用一個示例來展示用unittest指令碼是什麼樣子的。藉助Selenium IDE的錄製功能,可以完成這樣的操作。錄製完成後,我們按照如下步驟,將其匯出。
然後執行匯出的py檔案,可以得到類似下面的資訊:
---------------------------------------------------
Ran 1 test in 12.801s
OK
我們開啟匯出的指令碼看一看,錄製的是在百度裡面搜尋關鍵字Selenium2:
# -*- coding: utf-8 -*-
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import Select
from selenium.common.exceptions import NoSuchElementException
from selenium.common.exceptions import NoAlertPresentException
#匯入unittest包
import unittest, time, re
#SearchTest類繼承自unittest.TestCase,表明這是一個測試案例
class SearchTest(unittest.TestCase):
#setUp用於初始化工作
def setUp(self):
self.driver = webdriver.Firefox()
self.driver.implicitly_wait(30)
self.base_url = "https://www.baidu.com/"
self.verificationErrors = []
self.accept_next_alert = True
#以test開頭的是我們的測試指令碼
def test_search(self):
driver = self.driver
driver.get(self.base_url + "/")
driver.find_element_by_id("kw").click()
driver.find_element_by_id("kw").clear()
driver.find_element_by_id("kw").send_keys("selenium2")
driver.find_element_by_id("su").click()
def is_element_present(self, how, what):
try: self.driver.find_element(by=how, value=what)
except NoSuchElementException as e: return False
return True
def is_alert_present(self):
try: self.driver.switch_to_alert()
except NoAlertPresentException as e: return False
return True
def close_alert_and_get_its_text(self):
try:
alert = self.driver.switch_to_alert()
alert_text = alert.text
if self.accept_next_alert:
alert.accept()
else:
alert.dismiss()
return alert_text
finally: self.accept_next_alert = True
#在每個測試方法後執行,完成清理工作
def tearDown(self):
self.driver.quit()
self.assertEqual([], self.verificationErrors)
#整個測試過程集中在unittest的main()模組中,其預設執行以test開頭的方法
if __name__ == "__main__":
unittest.main()
通過這個我們大概對unittest有個直觀的瞭解了。unittest.main():使用它可以將一個單元測試模組變為可直接執行的測試指令碼,main()方法使用TestLoader類來搜尋所有包含在該模組中以“test”命名開頭的測試方法,並自動執行。執行方法的預設順序是:根據ASCII碼的順序載入測試用例,數字與字母的順序為:0-9,A-Z,a-z。所以以A開頭的測試用例方法會優先執行,以a開頭會後執行。
二、unittest中的概念
TestCase:
一個Testcase的例項就是一個測試用例,測試用例就是一個完整的測試流程,包括初始化setUp、執行run、測試後的還原tearDown。unittest.TestCase類,所有測試用例類繼承的基本類。此類提供了很多assert方法用於檢查比較,部分如下:
多數方法都可以見其名知其意,使用的門檻很低。
TestSuite:
對一個功能的測試往往需要多測試用例的,可以把多的測試用例集合在一起執行,這就是TestSuite的概念。常用addTest()方法將一個測試用例新增到測試套件中。
TextTestRunner:
是用來執行測試用例的,其中的run(test)用來執行TestSuite/TestCase。測試的結果會儲存在TextTestResult例項中。
TestFixture:
測試準備前要做的工作和測試執行完後要做的工作.包括setUp()和tearDown()。通過覆蓋TestCase的setUp和tearDown來實現。
知道了這幾個主要的概念,我們就可以把上面的指令碼中的最後一行unittest.main()
,改為以下程式碼:
#構造測試套件
suite = unittest.TestSuite()
suite.addTest(SearchTest("test_search"))
#執行測試
runner = unittest.TextTestRunner()
runner.run(suite)
執行之後發現和之前用unittest.main()的結果一樣。
三、用例組織
這裡我們假設,腳本當中有多個TestCase如test_case1,test_case2…,那我們應該怎樣去控制它們的執行順序呢?
執行測試用例方案一:
直接用unittest.main()
執行,這裡它搜尋所有以test開頭的測試用例方法,按照ASCII的順序執行多個用例。
執行測試用例方案二:
先例項化測試套件,將用例載入進去,再用TextTestRunner去執行用例:
suite=unittest.TestSuite()
suite.addTest(Test('test_case2'))
suite.addTest(Test('test_case1'))
runner=unittest.TextTestRunner()
runner.run(suite)
執行的順序是用例的載入順序,比如這裡是先執行2後執行1。
執行測試用例方案三:
在方案2中,如果我們有成百上千個用例的話,一個一個add進去,是不太現實的,那麼我們可以用defaultTestLoader來載入:
test_dir = './'
discover = unittest.defaultTestLoader.discover(test_dir, pattern='*test.py')
runner = unittest.TextTestRunner()
runner.run(discover)
這裡用./指定了當前目錄,指定了*test.py檔案,對其當中的用例進行執行,順序和方案一相同。
如果這裡指定的目錄下面有多個經pattern匹配上的.py檔案呢?呼叫discover方法,首先通過test_dir定義查詢目錄,如果檔名滿足定義的pattern,那麼我們要用for迴圈來找出所有被篩選出來的用例,並將其迴圈加到套件中,主要程式碼如下:
for test_suite in discover:
for test_case in test_suite:
test_unit.addTests(test_case)
由上面組織用例的方式我們可以知道,在實際的測試用指令碼開發中,我們可以在目錄下建立xx.py的檔案,當用例穩定執行後,可以修改成test_xx.py,以便於新增到測試套件中。注意,檔名的匹配規則,我們可以隨便由pattern引數定義。
如果要執行多級目錄結構的用例呢?要想被discover讀取執行,我們要在目錄下加_ init _.py檔案
四、一個例子
下面簡單的介紹一個用unittest組織的用例結構,先建立D:\Test_Project目錄,下面放上test_case和test_report來分別存放用例和報告。
1. 編寫測試用例
在test_case下面編寫用例,如下簡單的示範了在百度上搜索關鍵字和點選設定的操作:
檔名為:test_baidu.py
# -*- coding: utf-8 -*-
from selenium import webdriver
import unittest, time, re
class MyTest(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Firefox()
self.driver.implicitly_wait(30)
self.base_url = "https://www.baidu.com"
self.accept_next_alert = True
def test_02baidu_search(self):
u''' 測試百度搜索'''
driver = self.driver
driver.get(self.base_url + "/")
driver.find_element_by_id("kw").click()
driver.find_element_by_id("kw").clear()
driver.find_element_by_id("kw").send_keys("selenium-test")
driver.find_element_by_id("su").click()
print("test_baidu__test_02baidu_search")
def test_01baidu_setting(self):
u''' 測試百度首頁設定 '''
driver = self.driver
driver.get(self.base_url + "/")
driver.find_element_by_css_selector("div#u1 a.pf").click()
driver.find_element_by_class_name("setpref").click()
driver.find_element_by_css_selector("div#gxszButton>a.prefpanelgo").click()
driver.switch_to_alert().accept()
print("test_baidu__test_01baidu_setting")
def tearDown(self):
self.driver.close()
#從all_test中呼叫時,可以不要這個
if __name__ == "__main__":
unittest.main()
為了顯示出組織測試用例的效果,我們將此檔案再複製一份,把檔名和方法名等修改一下:
檔名為:test_baidu2.py
# -*- coding: utf-8 -*-
from selenium import webdriver
import unittest, time, re
class MyTest(unittest.TestCase):
u''' 測試baidu的第二個用例'''
def setUp(self):
self.driver = webdriver.Firefox()
self.driver.implicitly_wait(30)
self.base_url = "https://www.baidu.com"
self.accept_next_alert = True
def test_02baidu_search(self):
u''' 測試baidu的第二個用例的test_02baidu_search'''
driver = self.driver
driver.get(self.base_url + "/")
driver.find_element_by_id("kw").click()
driver.find_element_by_id("kw").clear()
driver.find_element_by_id("kw").send_keys("selenium-test")
driver.find_element_by_id("su").click()
print("test_baidu2__test_02baidu_search")
def test_01baidu_setting(self):
u''' 測試baidu的第二個用例的test_01baidu_setting'''
driver = self.driver
driver.get(self.base_url + "/")
driver.find_element_by_css_selector("div#u1 a.pf").click()
driver.find_element_by_class_name("setpref").click() driver.find_element_by_css_selector("div#gxszButton>a.prefpanelgo").click()
driver.switch_to_alert().accept()
print("test_baidu2__test_01baidu_setting")
def tearDown(self):
self.driver.close()
if __name__ == "__main__":
unittest.main()
~
2. 美化報告樣式和傳送結果郵件
上面我們寫了 兩個測試用例作為示例,我們也可以新增更多的進去。接著我們使用HTMLTestRunner這個開源模組來美化測試報告,關於它的下載使用可以參考https://pypi.python.org/pypi/HTMLTestRunner。然後,我們可以在程式碼中寫上執行完成之後自動傳送測試郵件出來,便於我們檢視。請參看以下程式碼:
#coding=utf-8
import unittest
import smtplib
from email.mime.text import MIMEText
from email.header import Header
import time
import HTMLTestRunner
from email.mime.application import MIMEApplication
#---傳送郵件---
def send_email(report_file):
sender = "[email protected]"
receiver = "[email protected]"
smtpserver = "smtp.qq.com"
#傳送郵箱的賬號密碼,此處使用的是qq郵箱和第三方登入的授權碼
username = "[email protected]"
password = "gfomcomojtuudijc"
#定義郵件正文
file = open(report_file,"rb")
mail_body = file.read()
file.close()
msg = MIMEText(mail_body, _subtype="html", _charset="utf-8")
msg["Subject"] = u"自動化測試報告"
smtp = smtplib.SMTP_SSL("smtp.qq.com")
smtp.login(username, password)
smtp.sendmail(sender, receiver, msg.as_string())
smtp.quit()
print("Email has send out !")
#---將用例新增到測試套件---
def creatsuite():
testunit=unittest.TestSuite()
test_dir = "D:\\Test_Project\\test_case"
discover = unittest.defaultTestLoader.discover(test_dir, pattern="test*.py",
top_level_dir = None)
for test_suite in discover:
for test_case in test_suite:
testunit.addTest(test_case)
print (testunit)
return testunit
if __name__ == "__main__":
current_time = time.strftime("%Y-%m-%d-%H-%M")
report_dir = "D:\\Test_Project\\test_report\\"
report_file = report_dir + current_time + "-Test_Result.html"
report_stream = open(report_file, "wb")
# runner = unittest.TextTestRunner()
# 注意HTMLTestRunner只支援python2
runner = HTMLTestRunner.HTMLTestRunner(stream=report_stream,title=u"自動化測試報告", description=u"用例執行情況如下:")
runner.run(creatsuite())
report_stream.close()
send_email(report_file)
在上面的程式碼中我們使用了runner = HTMLTestRunner.HTMLTestRunner()
方法來代替runner = unittest.TextTestRunner()
,是為了使用HTMLTestRunner這個模組來美化和輸出美觀的報告。然後呼叫方法來發送郵件。執行此檔案後,可以得到以下輸出的報告:
可以看見使用這個可以清晰的看到用例的執行情況,也便於檢視失敗用例的原因去除錯它。
同時,在我們輸入的收件箱裡也會受到一份通知郵件,我們可以將此輸出報告新增到郵件的正文或附件中,以便於檢視。