Selenium(十四):自動化測試模型介紹、模塊化驅動測試案例、數據驅動測試案例


1. 自動化測試模型介紹

隨着自動化測試技術的發展,演化為了集中模型:線性測試、模塊化驅動測試、數據驅動測試和關鍵字驅動測試。

下面分別介紹這幾種自動化測試模型的特點。

1.1 線性測試

通過錄制或編寫對應用程序的操作步驟產生相應的線性腳本,每個測試腳本相對獨立,且不產生其他依賴與調用,這也是早期自動化測試的一種形式:它們其實就是單純的來模擬用戶完整的操作場景。

前面寫的所有文章所編寫的測試腳本都屬於線性測試。

這種模型的優勢就是每一個腳本都是完整且獨立的。所以,任何一個測試用例腳本拿出來都可以單獨執行。當然,缺點也相當明顯,測試用例的開發與維護成本很高。

開發成本高,測試用例之間可能會存在重復的操作,不得不為每一個用例去錄制或編寫這些重復的操作。例如每個用例中重復的用戶登錄和退出操作等。

維護成本高,正因為測試用例之間存在重復的操作,所以當這些重復的操作發生改變時,就需要逐一的對它們進行修改。例如登錄輸入框的定位發生了改變,就需要對每一個包含登錄的用例進行調整。

1.2 模塊化驅動測試

正是由於線性測試的缺陷非常明顯,因此早期的自動化編程專家就考慮用新的自動化測試模塊來代替線性測試。做法也很簡單,借鑒了編程語言中模塊化的思想,把重復的操作獨立成公共模塊,當用例執行過程中需要用到這一門課操作時則被調用,這樣就最大限度的消除了重復,從而提高測試用例的可維護性。

提高了開發效率,不用重復編寫相同的操作腳本。例如,已經寫好一個登錄模塊,后續測試用例在需要登錄的地方調用即可。

簡化了維護的復雜性,加入登錄按鈕的定位發生了變化,那么只需修改登錄模塊的腳本即可,對於所有調用登錄模塊的測試腳本來說不需要做任何修改。

1.3 數據驅動測試

雖然模塊化驅動測試很好的解決了腳本的重復問題,但是自動化測試腳本在開發的過程中還是發型了諸多不便。例如,現在我要測試不同用戶的登錄,首先用的是“張三”的用戶名登錄,下一個測試用例要換成“李四”的用戶名登錄。在這種情況下,還是需要重復的編寫登錄腳本,因為雖然登錄的步驟相同,但是登錄所用的測試數據不同。

於是,數據驅動測試的概念就為解決這類問題而被提出。從它的本意來解釋,就是數據的改變從而驅動自動化測試的執行,最終引起測試結果的改變。這聽上去的確是個高大上的概念,而在早期的商業自動化工具中,也的確把這一概念作為一個賣點。對於數據驅動所需要的測試數據,也是通過根據工具內置的Datapool管理。

數據驅動說的直白點就是數據的參數化,因為熟人數據的不同從而引起輸出結果的不同。

不管我們讀取的是定義的數組、字典,或者是外部文件(Excel、csv、txt、xml等),都可以看作是數據驅動,它的目的就是實現數據與腳本的分離。

這樣做的好處同樣是顯而易見的,它進一步增強了腳本的復用性。同樣以道路為例,首先是重新設計登錄模塊,使其可以接受不同的數據,把接收到的數據作為登錄操作的一部分。這樣就可以很好的適應相同操作、不同的數據的情況。當指定登錄用戶是“張三”時,那么登錄之后的結果就是歡迎“張三”;當指定登錄用戶是“李四”時,登錄結果就顯示“歡迎李四”。這就是數據驅動所希望達到的目的。

1.4 關鍵字驅動測試

理解了數據驅動后,無非是把“數據”換成“關鍵字”,通過關鍵字的改變引起測試結果的改變。

目前市面上典型關鍵字驅動工具以OTP(目前已更名為UFT-Unified Funcionl Testing)、Robot Framework(RIDE)工具為主。這類工具封裝了底層的代碼,提供給用戶獨立的圖像界面,以“填表格”的形式免除測試人員對寫代碼的恐懼,從而降低腳本的編寫難度,我們只需使用工具所提供的關鍵字以“過程式”的方式來編寫用例即可。(我公司使用的是Robot Framework)

當然,selenium家族中的selenium IDE也可以看作是一種傳統的關鍵字驅動的自動化工具。

2. 模塊化驅動測試案例

通過對自動化測試模型的介紹,我們了解了模塊化設計的優點。這里我們就以具體的例子來介紹模塊的具體應用,當然它的基礎是Python語言中函數與類方法的調用。 

線性測試代碼:

from selenium import webdriver

wd = webdriver.Chrome()
wd.implicitly_wait(10)
#進入某網站
wd.get('https://www.xx.com')

#登錄
wd.find_element_by_id("id1").clear()#防止輸入框里面有內容
wd.find_element_by_id("id1").send_keys("username")
wd.find_element_by_id("id2").clear()
wd.find_element_by_id("id2").send_keys("password")
wd.find_element_by_id("id3").click()

#進入網站后的操作
#......

#退出
wd.find_element_by_link_text("退出").click()
wd.quit()

從上述流程分析,很多功能都需要登錄之后才能進行,對於手工來說,測試人員在執行用例的過程中可以一次登錄后驗證多個功能再退出,但自動化測試的執行有別於手工測試的執行,需要保持測試用例的獨立性和完整性,所以每一條用例在執行時都需要登錄和退出操作。這個時候就可以把登錄和退出的操作封裝為公共函數。當每一條用例需要登錄/退出時,只需調用它們即可,從而消除代碼重復,提高腳本的可維護性。

下面對登錄和退出進行模塊封裝。

from selenium import webdriver

#登錄
def login():  
    wd.find_element_by_id("id1").clear()#防止輸入框里面有內容
    wd.find_element_by_id("id1").send_keys("username")
    wd.find_element_by_id("id2").clear()
    wd.find_element_by_id("id2").send_keys("password")
    wd.find_element_by_id("id3").click()
    
#退出
def logout():
    wd.find_element_by_link_text("退出").click()
    wd.quit()

wd = webdriver.Chrome()
wd.implicitly_wait(10)
#進入某網站
wd.get('https://www.xx.com')
login()#調用登錄模塊

#進入網站后的操作
#......

logout()#調用退出模塊

現在將登錄的操作步驟封裝到login()函數中,把退出的操作封裝到logout()函數中,對於用例本身只需要調用這兩個函數即可,可以把更多的注意力放到用例本身的操作步驟中。

當然,如果只是把操作步驟封裝成函數並沒簡便太多,我們想要將其放到單獨的腳本文件中供其他用例調用。

public.py:

class Login():

    # 登錄
    def login(self,driver):
        driver.find_element_by_id("id1").clear()  # 防止輸入框里面有內容
        driver.find_element_by_id("id1").send_keys("username")
        driver.find_element_by_id("id2").clear()
        driver.find_element_by_id("id2").send_keys("password")
        driver.find_element_by_id("id3").click()

    # 退出
    def logout(self,driver):
        driver.find_element_by_link_text("退出").click()
        driver.quit()

當函數被獨立到單獨的腳本文件中時做了一點調整,主要是為函數增加了瀏覽器驅動的形參。因為函數實現的操作需要通過瀏覽器驅動driver,driver需要通過具體調用的用例給定。

from selenium import webdriver
from helloworld.public import Login

wd = webdriver.Chrome()
wd.implicitly_wait(10)
#進入某網站
wd.get('https://www.xx.com')

Login.login(wd)#調用登錄模塊

#進入網站后的操作
#......

Login.logout(wd)#調用退出模塊

首先,需要導入當前目錄下public.py文件中的Login()類,在需要的位置調用類中的login()和logout()函數。這樣對於每個用例的編寫與維護就方便了很多。

3. 數據驅動測試案例

前面提到關於數據驅動的形式有很多,我們既可以通過定義變量的方式進行參數化,也可以通過定義數組、字典的方式進行參數化,還可以通過讀取文件(txt\csv\xml)的方式進行參數化。

3.1 參數化登錄

現在的需求是測試不同用戶的登錄。對於測試用例來說,不變的是登錄的步驟,變化的是每次登錄的用戶名和密碼,這種情況下就需要用到數據驅動方式來編寫測試用例。基於前面的例子做如下修改。

public.py:

class Login():

    # 登錄
    def login(self,driver,username,password):
        driver.find_element_by_id("id1").clear()  # 防止輸入框里面有內容
        driver.find_element_by_id("id1").send_keys(username)
        driver.find_element_by_id("id2").clear()
        driver.find_element_by_id("id2").send_keys(password)
        driver.find_element_by_id("id3").click()

    # 退出
    def logout(self,driver):
        driver.find_element_by_link_text("退出").click()
        driver.quit()

修改login()方法的形參,為其增加username、password的形參,將得到的具體參數作為登錄時的數據。

from selenium import webdriver
from helloworld.public import Login

class LoginTest():

    def __init__(self):
        self.wd = webdriver.Chrome()
        self.wd.implicitly_wait(10)
        self.wd.get('https://www.xx.com')

    #test1用戶登錄
    def test1_login(self):
        username = 'test1'
        password = '123'
        Login().logout(self.wd,username,password)
        self.wd.quit()


    def test2_login(self):
        username = 'test2'
        password = '321'
        Login().logout(self.wd, username, password)
        self.wd.quit()

LoginTest.test1_login()
LoginTest.test2_login()

創建LoginTest類,並在__init__()方法中初始化瀏覽器驅動、等待超時長和URL等。這樣test1_login()與test2_login()兩個測試方法只需關注登錄的用戶名和密碼,通過調研Login()類的login()方法並傳入具體的參數來測試不同的用戶的登錄。

3.2 參數化搜索關鍵字

再來看一個百度搜索的例子。我們每天上網一般要用很多次百度搜索,而我們每次在使用百度搜索時步驟都是一樣的,不一樣的是每一次搜索的“關鍵字”不同。下面我們就以數組的方式對搜索的關鍵字進行參數化。

from selenium import webdriver

search_text = ['python','中文','text']

for text in search_text:
    wd = webdriver.Chrome()
    wd.implicitly_wait(10)
    wd.get("http://www.baidu.com")
    wd.find_element_by_id('kw').send_keys(text)
    wd.find_element_by_id('su').click()

這個例子比較簡單,首先創建一個數組search_text用來存放搜索的關鍵字,通過for循環來遍歷數組,最后把遍歷的數組元素作為每次百度搜索的關鍵字。這個例子可以更充分的體現出數據驅動的概念,因為測試數據的不同從而引起測試結果的不同。 

3.3 讀取txt文件

txt文件是我們經常操作的文件類型,Python提供了以下幾種讀取txt文件的方式。

read():讀取整個文件

readline():讀取一行數據

readlines():讀取所有行的數據 

回到前面的登錄案例,現在使用txt文件來存放用戶名和密碼數據,並通過讀取該文件中的數據作為用例的測試數據。

 txt:

xiaohuihui1,123
xiaohuihui2,456
xiaohuihui3,789

首先將用戶名和密碼按行寫入txt文件中,這里把用戶名和密碼用逗號“,”隔開。

代碼:

user_file = open('user_info.txt','r')
lines = user_file.readlines()
user_file.close()

for line in lines:
    username = line.split(',')[0]
    password = line.split(',')[1]
    print(username,password)

運行結果:

首先通過open()方法以讀(“r”)的形式打開user_info.txt文件,使用readlines()方法按行讀取txt文件,將獲取到的每一行數據通過split()方法拆分出用戶名和密碼。split()可以將一個字符串通過某一個字符為分割點拆分成左右兩部分,這里以逗號(,)為分割點。split()拆分出來的左右兩部分以數組的形式存放,所以[0]可以取到左半部分的字符串,[1]可以取到右半部分的字符串。

在上面的例子中循環遍歷出每一行數據的用戶名和密碼,得到想要的數據后就可以將其用於自動化測試腳本了。

3.4 讀取csv文件

那么新的問題來了,假設現在每次要讀取的是一組用戶數據,這一組數據包括用戶名、郵箱、年齡、性別等學習,這時再使用txt文件來存放這些數據,讀取起來就沒那么方便了。對於這種類型的數據可以通過CSV文件來存放。

創建info.csv文件,首先通過WPS表格或Excel創建表格,文件另存為CSV格式進行保存。注意不要通過直接修改文件的后綴名來創建CSV文件,這樣創建的並非真正的CSV類型的文件。

下面編寫python代碼進行循環讀取。 

import csv #導入csv包

#讀取本地CSV文件
date = csv.reader(open('info.csv','r'))

#循環輸出每一行信息
for user in date:
    print(user)

運行結果:

 

首先導入cvs模塊,通過reader()方法讀取CSV文件,然后通過for循環遍歷文件中的每一行數據。

從打印結果可以看出,讀取的每一行數據均是以數組的形式存儲的。如果想取用戶的某一列數據,只需要指定數組下標即可。 

import csv #導入csv包

#讀取本地CSV文件
date = csv.reader(open('info.csv','r'))

#循環輸出每一行信息
for user in date:
    print(user[1])

運行結果:

 

假如現在需要所有用戶的郵箱地址,那么只需指定郵箱所在列的下標即可。數組下標是以0開始的,郵箱位於數組的第二列,所以指用戶郵箱下標為[1]。

通過這種CVS文件來存放數據可以方便的解決讀取多列數據的問題。當然,用Excel文件存放這些數據也是一個不錯的選擇,只是所調用的模塊就需要從csv切換為xlrd,針對Excel文件操作的方法也會有所不同。

3.5 讀取xml文件 

有時候我們需要讀取的數據是不規則的。例如,我們需要一個配置文件來配置當前自動化測試腳本的URL、瀏覽器、登錄的用戶名和密碼等,這時候就可以考慮選擇使用XML文件來存放這些信息。

info.xml:

<?xml version="1.0" encoding="utf-8"?>
<info>
    <base>
        <platform>Windows</platform>
        <browser>Chrome</browser>
        <url>http://baidu.com</url>
        <login username="admin" password="123456"/>
        <login username="guest" password="654321"/>
    </base>
    <test>
        <province>北京</province>
        <province>廣東</province>
            <city>深圳</city>
            <city>珠海</city>
        <province>浙江</province>
            <city>杭州</city>
    </test>
</info>

下面以info.xml為例介紹讀取XML文件的方法。

3.5.1 獲取標簽信息

from xml.dom import minidom

#打開xml文檔
dom = minidom.parse("info.xml")

#得到文檔元素對象
root = dom.documentElement

print(root.nodeName)
print(root.nodeValue)
print(root.nodeType)
print(root.ELEMENT_NODE)

運行結果:

首先導入xml的minidom模塊,用來處理XML文件,parse()用於打開一個XML文件,documentElement用於得到XML文件的唯一根元素。

每一個節點都有它的nodeName、nodeValue、nodeType等屬性。nodeName為節點名稱,nodeValue為節點的值,支隊文本節點有效,nodeType為節點的類型。

3.5.2 獲得任意標簽名

from xml.dom import minidom

#打開xml文檔
dom = minidom.parse("info.xml")

#得到文檔元素對象
root = dom.documentElement

tagname = root.getElementsByTagName('browser')
print(tagname[0].tagName)

tagname = root.getElementsByTagName('login')
print(tagname[1].tagName)

tagname = root.getElementsByTagName('province')
print(tagname[2].tagName)

運行結果:

 

getElementByTagName()可以通過標簽名獲取標簽,它所獲取的對象是以數組形式存放。假如“login”和“province”標簽在info.xml文件中有多個,則可以通過指定數組的下標的方式獲取某個具體標簽。

getElementByTagName('province'):獲得的是標簽名為“province”的一組標簽

getElementByTagName('province').tagname[0]:表示一組標簽中的第一個

getElementByTagName('province').tagname[2]:表示一組標簽中的第三個

3.5.3 獲得標簽的屬性值

from xml.dom import minidom

#打開xml文檔
dom = minidom.parse("info.xml")

#得到文檔元素對象
root = dom.documentElement

logins = root.getElementsByTagName('login')

#獲得login標簽的username屬性值
username = logins[0].getAttribute("username")
print(username)

#獲得login標簽的password屬性值
password = logins[0].getAttribute("password")
print(password)

#獲得第二個login標簽的username屬性值
username = logins[1].getAttribute("username")
print(username)

#獲得第二個login標簽的password屬性值
username = logins[1].getAttribute("password")
print(password)

運行結果:

 

getAttribute()方法用於獲取元素的屬性值。它和WebDriver中所提供的get_attribute()方法相似。

3.5.4 獲得標簽對之間的數據

from xml.dom import minidom

#打開xml文檔
dom = minidom.parse("info.xml")

#得到文檔元素對象
root = dom.documentElement

provinces = dom.getElementsByTagName('province')
citys = dom.getElementsByTagName('city')

#獲得第二個province標簽對的值
p2 = provinces[1].firstChild.data
print(p2)

#獲得第一個city標簽對的值
c1 = citys[0].firstChild.data
print(c1)

#獲得第二個city標簽對的值
c2 = citys[1].firstChild.data
print(c2)

運行結果:

 

firstChild屬性返回被選節點的第一個子節點。data表示獲取該節點的數據,它和WebDriver中提供的text方法類似。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM