第六章 自動測試實施

  從本章開始,我們將講述如何實施自動化測試,在第一章的時候,我們也提供了自動化實施的步驟。那些兒步驟是指導方針,可以按着這一步步地去實施,可是有點兒籠統。本章我們將從具體實例入手,按前面的我們提到的步驟來講解,通過本章的學習,你可以從一個被測試的網站着手,從零開始建立起普通的迴歸測試用例。

6.1 評審被測試對象功能

      現在你的老大給你分配個任務:我們公司的業務已經成熟,網站主要功能也基本上不會變化了,我們想在每次上線後自動迴歸一下主功能。所以呢,我們想讓你對我們的網站建立起自動化測試,用例要求覆蓋主要功能。

當你接到這個任務的時候,你應該如何去做呢?不是去想用什麼來寫自動化,或是馬上用你擅長的語言馬上就去寫測試腳本。而是要先分析一下被測網站的功能,哪些兒是主要的,哪些兒必須自動化,哪些兒不能自動化。

現在我們以衆籌網(www.zhongchou.cn)爲例,分析一下主要功能:

1)登錄註冊

2)瀏覽項目

3)搜索項目

4)查看項目詳情

5)支持項目

6)個人信息設置

7)發起項目

8)查看自己相關的項目

9)發起的項目管理和訂單發貨

10)消息中心

等等,這十個主要功能是網站最基本的功能,然後我們分析一下哪些兒能自動化,哪些兒不能?

能自動化的部分:12346810

不能自動化的部分:579

   爲什麼不能自動化呢?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,我們將運行測試用例123;測試目的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上實現自動化運行。從編寫自動化測試用例,講述到實現無人值守的迴歸測試用例的建立,希望你能學到點兒實用的東西。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章