從本章開始,我們將講述如何實施自動化測試,在第一章的時候,我們也提供了自動化實施的步驟。那些兒步驟是指導方針,可以按着這一步步地去實施,可是有點兒籠統。本章我們將從具體實例入手,按前面的我們提到的步驟來講解,通過本章的學習,你可以從一個被測試的網站着手,從零開始建立起普通的迴歸測試用例。
6.1 評審被測試對象功能
現在你的老大給你分配個任務:我們公司的業務已經成熟,網站主要功能也基本上不會變化了,我們想在每次上線後自動迴歸一下主功能。所以呢,我們想讓你對我們的網站建立起自動化測試,用例要求覆蓋主要功能。
當你接到這個任務的時候,你應該如何去做呢?不是去想用什麼來寫自動化,或是馬上用你擅長的語言馬上就去寫測試腳本。而是要先分析一下被測網站的功能,哪些兒是主要的,哪些兒必須自動化,哪些兒不能自動化。
現在我們以衆籌網(www.zhongchou.cn)爲例,分析一下主要功能:
(1)登錄註冊
(2)瀏覽項目
(3)搜索項目
(4)查看項目詳情
(5)支持項目
(6)個人信息設置
(7)發起項目
(8)查看自己相關的項目
(9)發起的項目管理和訂單發貨
(10)消息中心
等等,這十個主要功能是網站最基本的功能,然後我們分析一下哪些兒能自動化,哪些兒不能?
能自動化的部分:1,2,3,4,6,8,10
不能自動化的部分:5,7,9
爲什麼不能自動化呢?5,支持項目,涉及到真實的支付,影響項目對賬,因爲不管是測試環境還是線上環境,都是真實的支付,如果有支付的測試環境,也是可以自動化的。
7,發起項目,發起項目通過審覈後,會在線上產生測試數據,影響用戶體驗。這違反了測試不能對線上產生垃圾數據的原則,所不不建議自動化,但是可以測試發起項目的流程,不對項目進行上線。
9,發起的項目管理和訂單發貨。發起項目我們就沒有自動化,所以這塊功能是沒有數據的,再者訂單發貨會短信通知支持者,會影響到用戶,不建議自動化。
當你接到任何一個測試任務的時候,需要先這樣分析一下被測試對象的功能及是否合適自動化測試。然後查找一下是否存在對應功能的手工測試用例,一般公司都會做測試用例的收集工作的。如果沒有,就先編寫對應的測試用例,然後再去轉化成自動化測試用例。
6.2 評審被測對象編碼
評審完了主功能,然後開始評審被測試對象的編碼,選擇與被測試對象相同或是同一系列的腳本語言來寫測試用例。一般網站編碼無非是ASP.NET,PHP,JSP等,編寫測試用例的腳本語言也可以用php,python,java等,根據實際情況,做出相應的選擇。
經查看,我們的衆籌網是用php編寫的,按原則來說,我們應該用php來編寫測試用例。Webdriver也支持php,但是php支持庫不多,在用例組織和報告生成等方面也存在着不足,所以目前用php編寫自動化測試用例的不多。綜合考慮各種因素,我們決定用python來編寫自動化測試用例。
6.3 自動化測試框架的選擇
目前業界做頁面自動化的都是選用selenium1.0 RC或是selenium2.0(webdriver),而它們之間是存在着區別的:
Selenium RC 工作原理:
(1)RC server 在服務端啓動 瀏覽器 並將Core 注入到瀏覽器中 (爲了解決瀏覽器的同源策略)
(2)我們的測試腳本調用Client API,Client將操作轉化成標準的selenese語句發送給RC Server。
(3)Selenium Core 解釋selenese語句,通過js的方式操作瀏覽器。
Webdriver 工作原理:
(1)WebDriver 啓動目標瀏覽器,並綁定到指定端口。該啓動的瀏覽器實例,做爲web driver的remote server。
(2)Client 端通過CommandExcuter發送HTTPRequest 給remote server 的偵聽端口(通信協議: the webriver wire protocol)。
(3)Remote server 需要依賴原生的瀏覽器組件(如:IEDriver.dll,chromedriver.exe),來轉化轉化瀏覽器的native調用。
那麼remote server端的這些功能是如何實現的呢?答案是瀏覽器實現了webdriver的統一接口,這樣client就可以通過統一的restful的接口去進行瀏覽器的自動化操作。目前webdriver支持ie, chrome, firefox, opera等主流瀏覽器,其主要原因是這些瀏覽器實現了webdriver約定的各種接口。
優缺點:
(1)RC 需要啓動一個 RCserver,就直觀感覺上多了一個步驟。
(2)RC 採用js 的方式,穩定性和兼容行方面還是取決與js的代碼的質量。有時需要自己去寫js代碼來擴展相應的功能。由於自己是從RC用起的,對於RC js的方式還是比較接受,感覺比較靈活。
(3)至於彈出對話框和警告框的處理,RC原生是不支持的。不過可以通過第三方軟件來解決,比如autoit。我用的就是autoit,使用簡單方便,是一個不錯的工具。
通過上面的分析,結合現在的時代潮流,我們採用高版本的selenium 2.0來作爲我們的自動化測試的框架。
6.4 自動化測試用例運行環境
由於我們採用的是python開發的腳本語言,對運行環境要求不是太高,可是相應的包還是要安裝的。在腳本編寫期間,我們一般是在自己機器上編寫和調試的,運行環境是windows環境。在後期的交付運行,自動執行迴歸的時候,我們要放到服務器上,服務器是linux環境的。
在Windows環境和linux環境下,python運行環境差別不大,所以不要太擔心環境的問題。可是也有需要注意的地方,比如說文件路徑的寫法,兩個環境下還是不同的。這也是我們一再要求用相對路徑,最好用函數來獲取路徑,而不是把路徑寫死到代碼中的原因。
6.5 自動化代碼架構的規劃
任何一個好的程序,都需要有好的規劃。考慮到現在的結構化程序設計,我們的自動化測試代碼也需要好好規劃一下架構,不能像記流水帳似的按測試用例的步驟羅列下來。
6.5.1 代碼架構規劃的原因
自動化測試用例的主要工作就是迴歸測試,由於其任務的特殊性,就決定了自動化測試用例是成體系的,大里的自動化測試用例放在一起,如果不好好規劃一下,會給後期的工作帶來很大的影響。
代碼規劃的原因:
(1)增加可讀性。根據用途,將代碼進行分類存放,方便團隊其他成員進行代碼管理與合作開發。
(2)便於維護。自動化測試用例不可能是一成不變的,被測試的網站變化了,我們對應的自動化測試用例也要進行相應的維護。如果不進行規劃,出現問題後不方便定位,更加會耗費大量的成本去維護。
(3)提高代碼利用率。結構化程序設計,要求我們對代碼重複利用率要高,同樣的代碼要寫在公用函數或是方便,減少代碼量。
6.5.2 如何規劃代碼架構
目前自動化的用法主要有兩種:1,簡單錄製回放轉化成的測試用例,此用例用於反覆測試的功能點,一旦功能上線後便不再用。目地是加快回歸測試的執行。特點就是對功能進行反覆測試,不考慮通用性,健壯性等。2,用於迴歸測試的系統化的測試用例。此種用例是需要反覆執行的,一定要考慮程序的通用性,健壯性以及執行時間等。
針對第一種測試用例,雖然使用時間比較短,通用率也不高,不過有些兒公司還是會收集這樣的測試用例的,以便以後同樣的或是類似的功能迴歸測試的時候用。此時可以按業務線對測試用例進行存放,測試用例中包含測試腳本和測試數據。
迴歸測試自動化測試用例是我們的重點,所以我們要詳細規劃一下:
(1)爲了增加代碼的通用性,我們把大部分測試用例需要執行的操作步驟封裝成函數,放到公共的類中,並把公共的類也按功能進行分類,統一放到一個文件夾下,如CommonFunction.
(2)將所有的測試用例放到一個文件夾下,測試用例與測試數據分離開,以便網站有變化的時候,我們只需要修改相應該的測試數據即可。測試用例放到一個文件夾下,如:TestCases。
(3)測試數據放到和測試用例同名的Xml文件中,然後通過公用的數據讀取方法,對測試數據進行讀取,然後在測試用例中調用通用的函數,讀取到的數據傳遞過去。所有數據存放到統一的文件夾下,如:TestData.
(4)測試用例集的存放。由於不同的測試目的,我們會運行不同的測試用例。如果測試目的A,我們將運行測試用例1,2,3;測試目的B ,我們運行測試用例1,3,4,5。所以我們會有不同的測試用例集文件,將這些兒文件統一存放到一個文件夾下,如:TestSuites.
(5)其他的資源文件,可以創建不同的文件夾來進行存放,如圖片文件夾image.
所以,經過規劃後,我們的測試用例結構如圖6.5.2所示,圖中的文件是我先前寫的測試用例。下面的章節,我們會具體講述測試用例:
圖6.5.2自動化測試用例代碼結構規劃
6.6 編寫自動化測試用例
我們規劃好了代碼架構,就可以編寫具體的測試用例了。首先,我們寫登錄的測試用例,在寫自動化測試用例的時候,我們要先寫一下公用函數類。根據需要,我們寫了三個通用的類放到CommonFunction文件夾下:WebDriverHelp用來存放所有頁面操作用到公用方法;QT_Operations用來存放具體操作功能塊,如登錄,退出等;DataOperations用來存放所有數據操作,用來讀取xml中的測試數據。
然後我們在TestCases文件夾下編寫登錄的測試用例TestCase_QT_Login.py執行具體的測試操作,驗證測試用例執行是否成功。
並把測試用例需要的測試數據,放到TestData文件夾下TestCase_QT_Login.xml文件中。如果網站有所變化,可以修改這個裏面的測試數據,從而減少代碼維護的成本。
6.6.1 WebDriverHelp類的內容
WebDriverHelp是我們所有測試用例能用到的方法,可以在編寫測試過程中不斷抽象出來,寫到這個類裏面,相當於對Webdriver再次做了一下封裝。
下面是部分代碼,以供大家參考:
@author:songxianfeng
@copyright: V1.0
'''
import time
import datetime
from selenium import webdriver
fromselenium.webdriver.support.ui import Select
fromselenium.common.exceptions import NoSuchElementException
fromselenium.webdriver.common.action_chains import ActionChains
fromselenium.webdriver.common.keys import Keys
global G_WEBDRIVER,G_BROWSERTYTPE,DRIVER
class WebDriverHelp(object):
'''
本類主要完成頁面的基本操作,如打開指定的URL,對頁面上在元素進行操作等
'''
def __init__(self,btype="close",atype="firefox",ctype="local"):
'''
根據用戶定製,打開對應的瀏覽器
@parambType: 開關參數,如果爲close則關閉瀏覽器
@paramaType:打開瀏覽器的類型,如chrome,firefox,ie等要測試的瀏覽器類型
@paramcType:打開本地或是遠程瀏覽器: local,本地;notlocal:遠程 '''
global DRIVER
if( btype == "open" ):
if( atype == "chrome" ):
if(ctype == "local"):
DRIVER = webdriver.Chrome()
DRIVER.maximize_window()
elif(ctype == "notlocal"):
DRIVER =webdriver.Remote(command_executor='http://124.65.151.158:4444/wd/hub',desired_capabilities=webdriver.DesiredCapabilities.CHROME)
DRIVER.maximize_window()
elif( atype == "ie" ):
if(ctype == "local"):
DRIVER =webdriver.Ie()
DRIVER.maximize_window()
elif(ctype == "notlocal"):
DRIVER =webdriver.Remote(command_executor='http://124.65.151.158:4444/wd/hub',desired_capabilities=webdriver.DesiredCapabilities.INTERNETEXPLORER)
DRIVER.maximize_window()
elif( atype == "firefox" ):
if(ctype == "local"):
DRIVER =webdriver.Firefox()
DRIVER.maximize_window()
elif(ctype == "notlocal"):
DRIVER =webdriver.Remote(command_executor='http://10.20.5.56:4444/wd/hub',desired_capabilities=webdriver.DesiredCapabilities.FIREFOX)
DRIVER.maximize_window()
self.DRIVER = DRIVER
def setup(self,logintype):
'''
定製測試URL
@paramloginplace: 指定測試的URL: qiantai:前臺測試地址,houtai:後臺測試地址,zhengshi:正式環境測試地址
ysh:原始會測試地址 zhengshiysh:正式原始會測試地址
'''
try:
qiantai_url = "http://test.zhongchou.cn"
ysh_url = "http://test.ysh.zhongchou.cn"
houtai_url = "http://test.admin.zhongchou.cn"
zhengshi_url = "http://www.zhongchou.cn"
zhengshi_ysh_url = "http://ysh.zhongchou.cn"
if(logintype=="qiantai"):
self.DRIVER.get(qiantai_url)
elif(logintype=="houtai"):
self.DRIVER.get(houtai_url)
elif(logintype=="zhengshi"):
self.DRIVER.get(zhengshi_url)
elif(logintype=="ysh"):
self.DRIVER.get(ysh_url)
elif(logintype=="zhengshiysh"):
self.DRIVER.get(zhengshi_ysh_url)
else:
print'路徑錯誤!'
self.DRIVER.implicitly_wait(1)
except NoSuchElementException:
print'您選擇的測試地址出錯!!'
def teardown(self):
'''
關閉瀏覽器
'''
self.DRIVER.quit()
def geturl(self,url):
'''
打開指定的網址
@param url:要打開的網址
'''
self.DRIVER.get(url)
def selectvalue(self,findby,select,selectvalue):
'''
通過定製定位方法和要選擇項的文本,選擇指定的項目
@paramfindby:定位方法,如:byid,byname,byclassname等
@paramselect: 要執行選擇操作的下拉框句柄
@paramselectvalue: 下拉框中要選擇項的文本
'''
if(findby == 'byid'):
select = Select(self.findelementbyid(select))
elif(findby =='byname'):
select = Select(self.findelementbyname(select))
elif(findby =='byclassname'):
select = Select(self.findelementbyclassname(select))
select.select_by_visible_text(selectvalue)
def inputvalue(self,findby,elmethod,value):
'''
通過定製定位方法,在輸入框中輸入值
@paramfindby: 定位方法,如:byid,byname,byclassname,byxpath等
@paramelmethod: 要定位元素的屬性值,如:id,name,class name,xpath等
@paramvalue: 要給文本框輸入的值
'''
if(findby == 'byid'):
self.findelementbyid(elmethod).send_keys(value)
elif(findby == 'byname'):
self.findelementbyname(elmethod).send_keys(value)
elif(findby =='byclassname'):
self.findelementbyclassname(elmethod).send_keys(value)
elif(findby == 'byxpath'):
self.findelementbyxpath(elmethod).send_keys(value)
……
上面的代碼都有很詳細的註釋,在此就不一一講述了。
6.6.2 QT_Operations類的內容
QT_Operations類是業務相關的公用功能塊的封裝,以便增加函數的公用性,減少代碼量。例如,登錄,在很多測試用例的第一步都需要先登錄再操作的。所以你可以抽像出測試用例中的功能模塊,封裝後放到這個類中。
QT_Operations類的部分代碼展示:
#-*- coding: UTF-8 -*-
'''
Created on2014-12-15
@author:songxianfeng
@copyright: V1.0
'''
import time
import win32api
import win32con
fromWebDriverHelp importWebDriverHelp
class QT_Operations(object):
'''
衆籌前臺相關操作
'''
def login(self,userName,passwd):
'''
從首頁直接登錄
@param userName: 用戶名
@param passwd:密碼
@param type1:指示登錄方式,1爲從主頁登錄,2,從登錄頁登錄
'''
WebDriverHelp().clickitem("byclassname", "Js-showLogin")
time.sleep(3)
WebDriverHelp().clearvalue('byname','username')
WebDriverHelp().inputvalue('byname','username',userName)
WebDriverHelp().clearvalue('byname','user_pwd')
WebDriverHelp().inputvalue('byname','user_pwd',passwd)
time.sleep(1)
WebDriverHelp().clickitem("byclassname", "a-btn")
time.sleep(5)
def logout(self):
'''
退出登錄
'''
WebDriverHelp().geturl("http://www.zhongchou.cn/usernew-loginout")
……
展示部分只包含了登錄和退出功能,其他的功能可以根據測試用例的需要進行添加。
6.6.3 DataOperations類的內容
DataOperations類是對測試數據進行讀取的操作,我們是用xml來存放測試數據的,所以測試用例執行的時候,需要先將測試數據讀取出來,傳遞給相應的函數來對測試用例進行執行。然後根據執行的結果,判斷測試用例是否執行成功。
DataOperations的內容:
#-*- coding: UTF-8 -*-
'''
Created on 2014-12-15
@author:songxianfeng
@copyright: V1.0
'''
import MySQLdb
from xml.dom import minidom
global DOC,CONN
class DataOperations(object):
'''
數據讀取相關操作
'''
def __init__(self,filename):
'''
初始化xml文檔
'''
global DOC,CONN
DOC = minidom.parse("../testData/"+filename)
def readxml(self,ftagname,num,stagname):
'''
從指定的文件中中讀取指定節點的值
@paramftagname:起始節點的名稱,如:project
@param num:取與起始節點相同的第num個節點
@param stagname:起始節點下的二級節點
@return: 返回二級節點的值
'''
root = DOC.documentElement
message=root.getElementsByTagName(ftagname)[num]
return message.getElementsByTagName(stagname)[0].childNodes[0].nodeValue
def readxml_attribute(self,ftagname,num,stagname,attributeName):
'''
從all_case.xml文件中讀取節點的屬性值
@paramftagname:起始節點的名稱,如:project
@param num:取與起始節點相同的第num個節點
@paramstagname: 起始節點下的二級節點
@paramattributeName: 二級節點的屬性名
@return:返回二級節點指定的屬性值
'''
root = DOC.documentElement
message=root.getElementsByTagName(ftagname)[num]
returnmessage.getElementsByTagName(stagname)[0].getAttribute(attributeName)
python對xml的讀取操作,如果你不太明白,可以去自行學習相關的內容,要教程不講解相關的操作。
6.6.4 具體的測試用例
上面的公用方法類創建以後,我們就可以着手編寫具體的測試用例了。在TestCases文件夾下創建測試用例TestCase_QT_Login.py,然後編寫下面的測試步驟:
(1) 打開衆籌網。
(2) 點擊登錄按鈕,輸入用戶名和密碼。
(3) 驗證是否登錄成功,用戶暱稱是不是剛剛登錄的賬號。
(4) 退出登錄,關閉瀏覽器。
測試用例代碼如下:
-*- coding: UTF-8 -*-
'''
Created on2014-12-15
@author:songxianfeng
@version: v1.0
'''
import unittest
import time
#導入需要的公共函數類
fromCommonFunction.WebDriverHelp import WebDriverHelp
fromCommonFunction.DataOperations import DataOperations
fromCommonFunction.QT_Operations import QT_Operations
class testcases_login(unittest.TestCase):
'''
登錄檢測
'''
def setUp(self):
WebDriverHelp("open","firefox","local").setup("zhengshi")#打開瀏覽器,並打開衆籌網
def testlogin(self):
#登錄檢測
dataoper=DataOperations("TestCase_QT_Login.xml") #讀取測試數據
#登錄
QT_Operations().login(dataoper.readxml('login', 0, 'username'),
dataoper.readxml('login', 0, 'password'))
self.assertEqual(WebDriverHelp().gettext('byxpath',dataoper.readxml('login', 0, 'checkpoint1')),dataoper.readxml('login', 0, 'value1')) #判斷登錄是否成功
#退出
QT_Operations().logout()
time.sleep(5)
self.assertEqual(WebDriverHelp().gettext('byclassname',dataoper.readxml('login', 0, 'checkpoint2')),dataoper.readxml('login', 0, 'value2')) #判斷退出是否成功
def tearDown(self):
WebDriverHelp().teardown()#關閉瀏覽器
if __name__ == "__main__":
unittest.main()
經過我們上面的封裝,現在具體的測試用例只是傳參數,調用具體的函數,驗證執行結果。是不是非常簡單?有點兒像我們小時候玩積木,用一塊塊現成的積木堆積出魔幻的城堡。
6.6.5 測試用例的具體數據
由於我們把測試用例和測試數據完全分離開了,所以我們用和測試用例同名的文件名命名對應的測試數據文件。登錄測試用例的數據文件是TestCase_QT_Login.xml,具體內容如下:
<?xmlversion="1.0" encoding="UTF-8"?>
<TestData>
<login name="登錄測試數據">
<username>183*****905</username>
<password>*******</password>
<checkpoint1>//div[@id='Js-header-loginBtn']/span/i[2]</checkpoint1>
<value1>潛龍0318</value1>
<checkpoint2>Js-showLogin</checkpoint2>
<value2>登錄</value2>
</login>
</TestData>
我們將要驗證的數據,獲取驗證元素的Xpath都寫到這個裏面,這樣就算網頁有所變化,我們只需要改這個數據文件中對應的Xpath即可,不需要更改測試用例。這樣可以將網站變化影響到測試用例降到最低,同時也減少了我們維護測試用例的成本。
編寫完上面所有的代碼後,我們只要右擊測試用例,選擇Run as->python run,調試運行測試用例即可。
6.7 本章小結
本章我們講解了如何從接觸到一個網站開始,從零着手去編寫自動化測試用例。通過上面各個方法的考慮,選擇合適的框架,腳本語言,規劃代碼架構,編寫公用函數,並以衆籌網的登錄註冊爲例,我們編寫了具體的測試用例。通過這樣的講解,相信大家一定能編寫測試用例了,要建立測試用例集,不過是按照先前我們選擇的測試用例,全部轉化成測試用例代碼即可。第七章我們將講述測試用例集的組織,以及將測試用例放到代碼管理工具jenkins上實現自動化運行。從編寫自動化測試用例,講述到實現無人值守的迴歸測試用例的建立,希望你能學到點兒實用的東西。