一、概述

數(shù)據(jù)驅(qū)動的自動化測試
從數(shù)據(jù)文件讀取輸入數(shù)據(jù),通過變量的參數(shù)化,將測試數(shù)據(jù)傳入測試腳本,不同的數(shù)據(jù)文件對應不同的測試用例。在這種模式下數(shù)據(jù)和腳本分離,腳本的利用率、可維護性大大提高,數(shù)據(jù)的覆蓋率也較高,但受界面變化的影響仍然很大。
關(guān)鍵字驅(qū)動的自動化測試
關(guān)鍵字驅(qū)動測試是數(shù)據(jù)驅(qū)動測試的一種改進類型,它將測試邏輯按照關(guān)鍵字進行分解,形成數(shù)據(jù)文件,關(guān)鍵字對應封裝的業(yè)務邏輯。
主要關(guān)鍵字包括三類:被操作對象(Item)、操作(Operation)和值(value),依據(jù)不同對象還有其他對應參數(shù)。
關(guān)鍵字驅(qū)動的主要思想是:腳本與數(shù)據(jù)分離、界面元素名與測試內(nèi)部對象名分離、測試描述與具體實現(xiàn)細節(jié)分離。數(shù)據(jù)驅(qū)動的自動化測試框架在受界面影響方面,較數(shù)據(jù)驅(qū)動和錄制/回放有明顯的優(yōu)勢,可根據(jù)界面的變化更新對應的關(guān)鍵字對象,而不用重新錄制腳本。
二、 數(shù)據(jù)驅(qū)動模式—DDT
1、核心原理
程序不變,數(shù)據(jù)變
即:多個測試用例的執(zhí)行過程和操作一樣的,只不過測試使用的數(shù)據(jù)和驗證結(jié)果有所不同,數(shù)據(jù)驅(qū)動就是把測試數(shù)據(jù)與測試腳本進行分離,把數(shù)據(jù)放到配置文件中
2、適用場景
測試過程比較簡單,但是需要使用大量的測試數(shù)據(jù)進行輸入驗證,適合個人測試
3、ddt安裝
ddt是python的第三方庫,安裝用命令:pip install ddt 即可
4、ddt模塊
ddt模塊包含類的裝飾器ddt和兩個方法裝飾器data
- ddt.ddt:裝飾類,也就是繼承TestCase的類
- ddt.data:裝飾測試方法,參數(shù)是一系列的值
- ddt.file_data:裝飾測試方法,參數(shù)是文件名。文件可以是json或者yaml類型
-- ① 如果文件是以“.yml”或者".yaml"結(jié)尾,ddt會作為yaml類型處理,其他文件都會作為json文件處理
-- ② 如果文件是列表,列表的值會作為測試用例參數(shù),同時,會作為測試用例方法名后綴顯示
-- ③ 如果文件是字典,字典的key會作為測試用例方法的后綴顯示,字典的value會作為測試用例參數(shù) - ddt.unpack:傳遞的是復雜的數(shù)據(jù)結(jié)構(gòu)時使用,比如使用列表或者元組,添加unpack后,ddt會自動把元組或者列表對應到多個參數(shù)上
5、案例演示
方式1:參數(shù)直接放在執(zhí)行腳本文件里
# 文件名:Internal_parameters_ddt.py
import unittest
import ddt
from selenium import webdriver
import time
from selenium.webdriver.common.by import By
@ddt.ddt
class Praddt(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome()
def tearDown(self):
self.driver.quit()
@ddt.data(
["數(shù)據(jù)驅(qū)動測試", "DDT"],
["關(guān)鍵字驅(qū)動測試", "51"],
["混合驅(qū)動測試", "python"]
)
@ddt.unpack
def test_ddt(self, search_word, expect_word):
self.driver.get('http://www.baidu.com')
self.driver.find_element(By.ID,"kw").send_keys(search_word)
self.driver.find_element(By.ID,"su").click()
time.sleep(2)
assert expect_word in self.driver.page_source
if __name__ == '__main__':
unittest.main()
方式2:參數(shù)放在執(zhí)行腳本外部數(shù)據(jù)文件里
外部數(shù)據(jù)文件data.txt里面的數(shù)據(jù)如下:
數(shù)據(jù)驅(qū)動測試;;;DDT
關(guān)鍵字驅(qū)動測試;;;51
混合驅(qū)動測試;;;python
執(zhí)行腳本文件代碼如下:
# 文件名:External_parameters_ddt.py
from selenium import webdriver
import time
import sys
# 讀取data.txt文件
from selenium.webdriver.common.by import By
def get_test_datas(file_path):
with open(file_path,encoding='utf-8') as fp:
test_datas = fp.readlines()
return test_datas
# 獲取數(shù)據(jù)文件中的search_word和expect_word
test_datas = get_test_datas('data.txt')
if len(test_datas) == 0:
print('測試數(shù)據(jù)文件數(shù)據(jù)為空,請檢查后再測試')
sys.exit()
# 每獲取一組數(shù)據(jù)后,都執(zhí)行下面的測試步驟
for i in range(len(test_datas)):
search_word,expect_word = test_datas[i].strip().split(';;;') # 去掉列表數(shù)據(jù)中的“\n”、“;;;”
try:
driver = webdriver.Chrome()
driver.get('http://www.baidu.com')
driver.find_element(By.ID,"kw").send_keys(search_word)
driver.find_element(By.ID,"su").click()
time.sleep(2)
assert expect_word in driver.page_source
driver.quit()
except AssertionError:
print('沒有找到斷言內(nèi)容{}'.format(expect_word))
driver.quit()
except Exception as e:
print('出現(xiàn)未知錯誤')
driver.quit()
二、關(guān)鍵字驅(qū)動模式—KDT
1、核心原理
把函數(shù)名稱和程序進行分離
關(guān)鍵字是指測試步驟中的某個動作,將其封裝成函數(shù)的名稱,即:把函數(shù)名放在配置文件中,然后從配置文件中讀出函數(shù)名稱以及函數(shù)對應的參數(shù),組合成函數(shù)調(diào)用表達式來進行函數(shù)的調(diào)用
測試步驟由:關(guān)鍵字、操作對象的定位表達式、操作值 三個部分組成。將這三個部分通過字符串拼接的方式,拼成一個函數(shù)的調(diào)用,以此來執(zhí)行測試步驟的執(zhí)行
例如:
測試步驟:在百度輸入框輸入“數(shù)據(jù)驅(qū)動測試”
拼接調(diào)用:input||kw||數(shù)據(jù)驅(qū)動測試===>input(‘kw’,‘數(shù)據(jù)驅(qū)動測試’)
2、適用場景
可以在應用未提交測試之前,就可以建立關(guān)鍵字驅(qū)動測試用例對象庫,適合大團隊大項目里面實施,可以讓不懂代碼的測試人員也能做自動化測試。RobotFramework自動化測試框架采用的模式就是關(guān)鍵字驅(qū)動,常用于UI自動化測試。
3、案例演示
需求場景1:用谷歌瀏覽器打開百度,輸入一個詞進行搜索,再對搜索結(jié)果進行校驗
解決方案:將以上步驟用代碼一一實現(xiàn)(面向過程)
# 文件名:kdt_v1.py
from selenium import webdriver
import time
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
driver.get('http://www.baidu.com')
search_box = driver.find_element(By.ID,"kw")
search_box.send_keys('數(shù)據(jù)驅(qū)動測試')
submit_button = driver.find_element(By.ID,"kw")
submit_button.click()
time.sleep(2)
assert 'DDT' in driver.page_source
driver.quit()
需求場景2:用谷歌瀏覽器打開百度,輸入不同的詞進行搜索,再對搜索結(jié)果進行一一校驗,如果將以上操作步驟代碼又重復寫一遍的話,就會顯得很繁瑣了
解決方案:將重復的操作步驟代碼進行封裝,直接通過txt文檔寫測試用例(面向?qū)ο螅?/p>
# 文件名:kdt_v2.py
from selenium import webdriver
import time
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
class KeyWord:
_solit_chr = ";;;"
def open_browser(self,browser_name):
if 'chrome' in browser_name.lower():
self.driver = webdriver.Chrome()
elif 'ie' in browser_name.lower():
self.driver = webdriver.Ie()
else:
self.driver = webdriver.Firefox()
self.wait = WebDriverWait(self.driver, 10)
def find_element(self, loc: str):
value, *by = loc.split(self._solit_chr)
if not by:
by = By.XPATH
else:
by = getattr(By, by[0])
ele = self.wait.until(lambda _: self.driver.find_element(by, value))
return ele
def visit(self,url):
self.driver.get(url)
def input(self,element,value):
self.find_element(element).send_keys(value)
def click(self,element):
self.find_element(element).click()
def sleep(self,seconds):
time.sleep(int(seconds))
def assertion(self,expect_word):
assert expect_word in self.driver.page_source
def quit(self):
self.driver.quit()
# 調(diào)試
if __name__ == '__main__':
kdt = KeyWord()
kdt.open_browser('chrome')
kdt.visit('http://www.baidu.com')
kdt.input('kw;;;ID','數(shù)據(jù)驅(qū)動測試')
kdt.click('su;;;ID')
kdt.sleep(2)
kdt.assertion('DDT')
quit()
修改后的代碼看起來貌似是比前面的代碼多了很多行,但是這里的核心思想是代碼可以復用,具體怎么復用呢?此時我們需要新建一個測試用例文件test_steps.txt,里面內(nèi)容如下:
open_browser||chrome
visit||https://www.baidu.com/
input||kw;;;ID||數(shù)據(jù)驅(qū)動測試
click||su;;;ID
sleep||2
assertion||DDT
quit
然后在封裝的代碼里面添加讀取測試用例文件的函數(shù),實現(xiàn)關(guān)鍵字驅(qū)動測試
關(guān)鍵字驅(qū)動的核心是:將自然語言(如: open_browser||chrome) 轉(zhuǎn)換成 (如:open_browser(“chrome”) )函數(shù)的調(diào)用
具體函數(shù)的實現(xiàn)思路拆解:
- ① 定義test_steps.txt,所有的測試步驟(關(guān)鍵字實現(xiàn)的)
- ② 框架程序要讀test_steps.txt,所有的行放到一個列表中,列表中的每一個元素是文件中的一行
test_steps = [“open_browser||chrome\n”,“visit||https://www.baidu.com/\n”,…] - ③ 判斷test_steps.txt列表中一共有多少元素,就知道有多少行,也就有多少個測試步驟
- ④ 使用for循環(huán),有多少個測試步驟,就循環(huán)多少次
- ⑤ 使用strip去掉換行符\n,使用split做切割分別得到關(guān)鍵字和對應參數(shù):
【情況1】:
有兩個“||”,比如:“input||kw;;;ID||數(shù)據(jù)驅(qū)動測試\n”
step = “input||kw||數(shù)據(jù)驅(qū)動測試\n”.strip().split("||")
得到結(jié)果:[‘input’, ‘kw;;;ID’, ‘數(shù)據(jù)驅(qū)動測試’]
keyword = step[0]
element = step[1]
value = setp[2]
【情況2】:
有一個“||”,比如:“open_browser||chrome\n”
step = “open_browser||chrome\n”.strip().split("||")
得到結(jié)果:[‘open_browser’, ‘chrome’]
keyword = step[0]
element = None
value = setp[1]
【情況3】:
沒有“||”,比如:“quit”
keyword = step[0]
- ⑥ 取到的幾個值,最終要拼成一個函數(shù)調(diào)用的字符串:
command = ‘open_browser(“chrome”)’
command = ‘input(“kw;;;ID”,“數(shù)據(jù)驅(qū)動測試”)’
command = ‘quit()’
… - ⑦ 用Python中的eval函數(shù)來調(diào)用字符串表達式
eval(command)
完整代碼如下:
# 文件名:kdt_v3.py
import sys
from selenium import webdriver
import time
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
class KeyWord:
_solit_chr = ";;;"
def open_browser(self,browser_name):
if 'chrome' in browser_name.lower():
self.driver = webdriver.Chrome()
elif 'ie' in browser_name.lower():
self.driver = webdriver.Ie()
else:
self.driver = webdriver.Firefox()
self.wait = WebDriverWait(self.driver, 10)
def find_element(self, loc: str):
value, *by = loc.split(self._solit_chr)
if not by:
by = By.XPATH
else:
by = getattr(By, by[0])
ele = self.wait.until(lambda _: self.driver.find_element(by, value))
return ele
def visit(self,url):
self.driver.get(url)
def input(self,element,value):
self.find_element(element).send_keys(value)
def click(self,element):
self.find_element(element).click()
def sleep(self,seconds):
time.sleep(int(seconds))
def assertion(self,expect_word):
assert expect_word in self.driver.page_source
def quit(self):
self.driver.quit()
def get_test_steps_data(file_path):
# 讀取文件數(shù)據(jù)
with open(file_path,encoding="utf-8-sig") as fp:
test_datas = fp.readlines()
return test_datas
test_steps = get_test_steps_data('test_steps.txt')
if len(test_steps) == 0:
print('測試步驟文件的數(shù)據(jù)為空,請檢查后再測試')
sys.exit()
kdt = KeyWord()
# 讀取的文件拼接成命令代碼
for i in test_steps:
if i.count('||') == 2:
keyword,element,value = i.strip().split('||')
command = 'kdt.{0}("{1}","{2}")'.format(keyword,element,value)
elif i.count('||') == 1:
keyword,value = i.strip().split('||')
command = 'kdt.{0}("{1}")'.format(keyword,value)
elif i.count("||") == 0:
keyword = i.strip()
command = 'kdt.{}()'.format(keyword)
try:
# 通過eval()函數(shù)執(zhí)行command的代碼
eval(command)
except:
flag = False
print('{} 測試用例執(zhí)行失敗'.format(command))
else:
print('{} 測試用例執(zhí)行成功'.format(command))
三、混合模式驅(qū)動模式—HDT
1、核心原理
數(shù)據(jù)驅(qū)動+關(guān)鍵字驅(qū)動=混合驅(qū)動
就是數(shù)據(jù)驅(qū)動和關(guān)鍵字驅(qū)動的聯(lián)合,既用到數(shù)據(jù)驅(qū)動模式也用到關(guān)鍵字驅(qū)動模式
2、適用場景
需要實現(xiàn):關(guān)鍵字(函數(shù)名)和程序分離、數(shù)據(jù)(包括函數(shù)需要的參數(shù))和程序分離的場景
測試人員只需在配置文件中維護好關(guān)鍵字和數(shù)據(jù)信息即可
3、案例演示
需求場景:用谷歌瀏覽器打開百度,輸入不同的詞進行搜索,再對搜索結(jié)果進行一一校驗(測試數(shù)據(jù)和測試步驟都放在配置文件中)
測試數(shù)據(jù)文件:test_datas.txt
{"search_word":"數(shù)據(jù)驅(qū)動測試", "expect_word":"DDT"}
{"search_word":"關(guān)鍵字驅(qū)動測試", "expect_word":"51"}
{"search_word":"混合驅(qū)動測試", "expect_word":"python"}
測試步驟文件:test_steps.txt
open_browser||chrome
visit||https://www.baidu.com/
input||kw;;;ID||{{search_word}}
click||su;;;ID
sleep||2
assertion||{{expect_word}}
quit
完整代碼如下:
# 文件名:hdt.py
import os
import re
import sys
from selenium import webdriver
import time
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
class KeyWord:
_solit_chr = ";;;"
def open_browser(self,browser_name):
if 'chrome' in browser_name.lower():
self.driver = webdriver.Chrome()
elif 'ie' in browser_name.lower():
self.driver = webdriver.Ie()
else:
self.driver = webdriver.Firefox()
self.wait = WebDriverWait(self.driver, 10)
def find_element(self, loc: str):
value, *by = loc.split(self._solit_chr)
if not by:
by = By.XPATH
else:
by = getattr(By, by[0])
ele = self.wait.until(lambda _: self.driver.find_element(by, value))
return ele
def visit(self,url):
self.driver.get(url)
def input(self,element,value):
self.find_element(element).send_keys(value)
def click(self,element):
self.find_element(element).click()
def sleep(self,seconds):
time.sleep(int(seconds))
def assertion(self,expect_word):
assert expect_word in self.driver.page_source
def quit(self):
self.driver.quit()
# 讀取測試數(shù)據(jù)
def get_test_datas(test_data_file_path):
if not os.path.exists(test_data_file_path):
print('{}測試數(shù)據(jù)文件不存在,請確認!'.format(test_data_file_path))
sys.exit(0)
test_datas = []
with open(test_data_file_path, encoding='utf-8-sig') as fp:
for line in fp:
test_datas.append(line.strip())
return test_datas
# 讀取測試步驟
def get_test_steps(test_steps_file_path):
if not os.path.exists(test_steps_file_path):
print('{}測試步驟文件不存在,請確認!'.format(test_steps_file_path))
sys.exit(0)
test_steps = []
with open(test_steps_file_path, encoding='utf-8-sig') as fp:
for line in fp:
test_steps.append(line.strip())
return test_steps
test_datas = get_test_datas('test_datas.txt')
kdt = KeyWord()
# 有幾行測試數(shù)據(jù),就執(zhí)行幾次測試步驟
for test_data in test_datas:
'''{"search_word":"數(shù)據(jù)驅(qū)動測試", "expect_word":"ddt"} 是json串,把json串轉(zhuǎn)換為字典類型'''
test_data = eval(test_data)
test_steps = get_test_steps('test_steps.txt')
for test_step in test_steps:
'''
1)把test_step中{{xxx}}里面的xxx找出來,用正則:re.search(r"{{(.*?)}}")
2)用test_data['xxx']把{{xxx}}替換掉
'''
if '{{' in test_step:
key = re.search(r"{{(.*?)}}", test_step).group(1)
test_step = re.sub(r"{{%s}}" % key, test_data[key], test_step)
print(test_step)
# 讀取的測試步驟拼接成命令代碼
if test_step.count('||') == 2:
keyword, element, value = test_step.strip().split('||')
command = 'kdt.{0}("{1}","{2}")'.format(keyword, element, value)
elif test_step.count('||') == 1:
keyword, value = test_step.strip().split('||')
command = 'kdt.{0}("{1}")'.format(keyword, value)
elif test_step.count("||") == 0:
keyword = test_step.strip()
command = 'kdt.{}()'.format(keyword)
try:
'''通過eval()函數(shù)執(zhí)行command的代碼'''
eval(command)
except:
flag = False
print('{} 測試用例執(zhí)行失敗'.format(command))
else:
print('{} 測試用例執(zhí)行成功'.format(command))