隨着自動化測試技術的發展,演化爲了集中模型:線性測試、模塊化驅動測試、數據驅動測試和關鍵字驅動測試。node
下面分別介紹這幾種自動化測試模型的特色。python
經過錄制或編寫對應用程序的操做步驟產生相應的線性腳本,每一個測試腳本相對獨立,且不產生其餘依賴與調用,這也是早期自動化測試的一種形式:它們其實就是單純的來模擬用戶完整的操做場景。web
前面寫的全部文章所編寫的測試腳本都屬於線性測試。編程
這種模型的優點就是每個腳本都是完整且獨立的。因此,任何一個測試用例腳本拿出來均可以單獨執行。固然,缺點也至關明顯,測試用例的開發與維護成本很高。數組
開發成本高,測試用例之間可能會存在重複的操做,不得不爲每個用例去錄製或編寫這些重複的操做。例如每一個用例中重複的用戶登陸和退出操做等。瀏覽器
維護成本高,正由於測試用例之間存在重複的操做,因此當這些重複的操做發生改變時,就須要逐一的對它們進行修改。例如登陸輸入框的定位發生了改變,就須要對每個包含登陸的用例進行調整。dom
正是因爲線性測試的缺陷很是明顯,所以早期的自動化編程專家就考慮用新的自動化測試模塊來代替線性測試。作法也很簡單,借鑑了編程語言中模塊化的思想,把重複的操做獨立成公共模塊,當用例執行過程當中須要用到這一門課操做時則被調用,這樣就最大限度的消除了重複,從而提升測試用例的可維護性。編程語言
提升了開發效率,不用重複編寫相同的操做腳本。例如,已經寫好一個登陸模塊,後續測試用例在須要登陸的地方調用便可。模塊化
簡化了維護的複雜性,加入登陸按鈕的定位發生了變化,那麼只需修改登陸模塊的腳本便可,對於全部調用登陸模塊的測試腳原本說不須要作任何修改。函數
雖然模塊化驅動測試很好的解決了腳本的重複問題,可是自動化測試腳本在開發的過程當中仍是髮型了諸多不便。例如,如今我要測試不一樣用戶的登陸,首先用的是「張三」的用戶名登陸,下一個測試用例要換成「李四」的用戶名登陸。在這種狀況下,仍是須要重複的編寫登陸腳本,由於雖然登陸的步驟相同,可是登陸所用的測試數據不一樣。
因而,數據驅動測試的概念就爲解決這類問題而被提出。從它的本意來解釋,就是數據的改變從而驅動自動化測試的執行,最終引發測試結果的改變。這聽上去的確是個高大上的概念,而在早期的商業自動化工具中,也的確把這一律念做爲一個賣點。對於數據驅動所須要的測試數據,也是經過根據工具內置的Datapool管理。
數據驅動說的直白點就是數據的參數化,由於熟人數據的不一樣從而引發輸出結果的不一樣。
無論咱們讀取的是定義的數組、字典,或者是外部文件(Excel、csv、txt、xml等),均可以看做是數據驅動,它的目的就是實現數據與腳本的分離。
這樣作的好處一樣是顯而易見的,它進一步加強了腳本的複用性。一樣以道路爲例,首先是從新設計登陸模塊,使其能夠接受不一樣的數據,把接收到的數據做爲登陸操做的一部分。這樣就能夠很好的適應相同操做、不一樣的數據的狀況。當指定登陸用戶是「張三」時,那麼登陸以後的結果就是歡迎「張三」;當指定登陸用戶是「李四」時,登陸結果就顯示「歡迎李四」。這就是數據驅動所但願達到的目的。
理解了數據驅動後,無非是把「數據」換成「關鍵字」,經過關鍵字的改變引發測試結果的改變。
目前市面上典型關鍵字驅動工具以OTP(目前已改名爲UFT-Unified Funcionl Testing)、Robot Framework(RIDE)工具爲主。這類工具封裝了底層的代碼,提供給用戶獨立的圖像界面,以「填表格」的形式免除測試人員對寫代碼的恐懼,從而下降腳本的編寫難度,咱們只需使用工具所提供的關鍵字以「過程式」的方式來編寫用例便可。(我公司使用的是Robot Framework)
固然,selenium家族中的selenium IDE也能夠看做是一種傳統的關鍵字驅動的自動化工具。
經過對自動化測試模型的介紹,咱們瞭解了模塊化設計的優勢。這裏咱們就以具體的例子來介紹模塊的具體應用,固然它的基礎是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()函數。這樣對於每一個用例的編寫與維護就方便了不少。
前面提到關於數據驅動的形式有不少,咱們既能夠經過定義變量的方式進行參數化,也能夠經過定義數組、字典的方式進行參數化,還能夠經過讀取文件(txt\csv\xml)的方式進行參數化。
如今的需求是測試不一樣用戶的登陸。對於測試用例來講,不變的是登陸的步驟,變化的是每次登陸的用戶名和密碼,這種狀況下就須要用到數據驅動方式來編寫測試用例。基於前面的例子作以下修改。
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()方法並傳入具體的參數來測試不一樣的用戶的登陸。
再來看一個百度搜索的例子。咱們天天上網通常要用不少次百度搜索,而咱們每次在使用百度搜索時步驟都是同樣的,不同的是每一次搜索的「關鍵字」不一樣。下面咱們就以數組的方式對搜索的關鍵字進行參數化。
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循環來遍歷數組,最後把遍歷的數組元素做爲每次百度搜索的關鍵字。這個例子能夠更充分的體現出數據驅動的概念,由於測試數據的不一樣從而引發測試結果的不一樣。
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]能夠取到右半部分的字符串。
在上面的例子中循環遍歷出每一行數據的用戶名和密碼,獲得想要的數據後就能夠將其用於自動化測試腳本了。
那麼新的問題來了,假設如今每次要讀取的是一組用戶數據,這一組數據包括用戶名、郵箱、年齡、性別等學習,這時再使用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文件操做的方法也會有所不一樣。
有時候咱們須要讀取的數據是不規則的。例如,咱們須要一個配置文件來配置當前自動化測試腳本的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文件的方法。
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爲節點的類型。
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]:表示一組標籤中的第三個
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()方法類似。
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方法相似。