一、概述
数据驱动的自动化测试
从数据文件读取输入数据,通过变量的参数化,将测试数据传入测试脚本,不同的数据文件对应不同的测试用例。在这种模式下数据和脚本分离,脚本的利用率、可维护性大大提高,数据的覆盖率也较高,但受界面变化的影响仍然很大。
关键字驱动的自动化测试
关键字驱动测试是数据驱动测试的一种改进类型,它将测试逻辑按照关键字进行分解,形成数据文件,关键字对应封装的业务逻辑。
主要关键字包括三类:被操作对象(Item)、操作(Operation)和值(value),依据不同对象还有其他对应参数。
关键字驱动的主要思想是:脚本与数据分离、界面元素名与测试内部对象名分离、测试描述与具体实现细节分离。数据驱动的自动化测试框架在受界面影响方面,较数据驱动和录制/回放有明显的优势,可根据界面的变化更新对应的关键字对象,而不用重新录制脚本。
二、 数据驱动模式—DDT
1、核心原理
程序不变,数据变
即:多个测试用例的执行过程和操作一样的,只不过测试使用的数据和验证结果有所不同,数据驱动就是把测试数据与测试脚本进行分离,把数据放到配置文件中
2、适用场景
测试过程比较简单,但是需要使用大量的测试数据进行输入验证,适合个人测试
3、ddt安装
ddt是python的第三方库,安装用命令:pip install ddt 即可
4、ddt模块
ddt模块包含类的装饰器ddt和两个方法装饰器data
- ddt.ddt:装饰类,也就是继承TestCase的类
- ddt.data:装饰测试方法,参数是一系列的值
- ddt.file_data:装饰测试方法,参数是文件名。文件可以是json或者yaml类型
-- ① 如果文件是以“.yml”或者".yaml"结尾,ddt会作为yaml类型处理,其他文件都会作为json文件处理
-- ② 如果文件是列表,列表的值会作为测试用例参数,同时,会作为测试用例方法名后缀显示
-- ③ 如果文件是字典,字典的key会作为测试用例方法的后缀显示,字典的value会作为测试用例参数 - ddt.unpack:传递的是复杂的数据结构时使用,比如使用列表或者元组,添加unpack后,ddt会自动把元组或者列表对应到多个参数上
5、案例演示
方式1:参数直接放在执行脚本文件里
# 文件名: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(
["数据驱动测试", "DDT"],
["关键字驱动测试", "51"],
["混合驱动测试", "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:参数放在执行脚本外部数据文件里
外部数据文件data.txt里面的数据如下:
数据驱动测试;;;DDT
关键字驱动测试;;;51
混合驱动测试;;;python
执行脚本文件代码如下:
# 文件名: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
# 获取数据文件中的search_word和expect_word
test_datas = get_test_datas('data.txt')
if len(test_datas) == 0:
print('测试数据文件数据为空,请检查后再测试')
sys.exit()
# 每获取一组数据后,都执行下面的测试步骤
for i in range(len(test_datas)):
search_word,expect_word = test_datas[i].strip().split(';;;') # 去掉列表数据中的“\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('没有找到断言内容{}'.format(expect_word))
driver.quit()
except Exception as e:
print('出现未知错误')
driver.quit()
二、关键字驱动模式—KDT
1、核心原理
把函数名称和程序进行分离
关键字是指测试步骤中的某个动作,将其封装成函数的名称,即:把函数名放在配置文件中,然后从配置文件中读出函数名称以及函数对应的参数,组合成函数调用表达式来进行函数的调用
测试步骤由:关键字、操作对象的定位表达式、操作值 三个部分组成。将这三个部分通过字符串拼接的方式,拼成一个函数的调用,以此来执行测试步骤的执行
例如:
测试步骤:在百度输入框输入“数据驱动测试”
拼接调用:input||kw||数据驱动测试===>input(‘kw’,‘数据驱动测试’)
2、适用场景
可以在应用未提交测试之前,就可以建立关键字驱动测试用例对象库,适合大团队大项目里面实施,可以让不懂代码的测试人员也能做自动化测试。RobotFramework自动化测试框架采用的模式就是关键字驱动,常用于UI自动化测试。
3、案例演示
需求场景1:用谷歌浏览器打开百度,输入一个词进行搜索,再对搜索结果进行校验
解决方案:将以上步骤用代码一一实现(面向过程)
# 文件名: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('数据驱动测试')
submit_button = driver.find_element(By.ID,"kw")
submit_button.click()
time.sleep(2)
assert 'DDT' in driver.page_source
driver.quit()
需求场景2:用谷歌浏览器打开百度,输入不同的词进行搜索,再对搜索结果进行一一校验,如果将以上操作步骤代码又重复写一遍的话,就会显得很繁琐了
解决方案:将重复的操作步骤代码进行封装,直接通过txt文档写测试用例(面向对象)
# 文件名: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()
# 调试
if __name__ == '__main__':
kdt = KeyWord()
kdt.open_browser('chrome')
kdt.visit('http://www.baidu.com')
kdt.input('kw;;;ID','数据驱动测试')
kdt.click('su;;;ID')
kdt.sleep(2)
kdt.assertion('DDT')
quit()
修改后的代码看起来貌似是比前面的代码多了很多行,但是这里的核心思想是代码可以复用,具体怎么复用呢?此时我们需要新建一个测试用例文件test_steps.txt,里面内容如下:
open_browser||chrome
visit||https://www.baidu.com/
input||kw;;;ID||数据驱动测试
click||su;;;ID
sleep||2
assertion||DDT
quit
然后在封装的代码里面添加读取测试用例文件的函数,实现关键字驱动测试
关键字驱动的核心是:将自然语言(如: open_browser||chrome) 转换成 (如:open_browser(“chrome”) )函数的调用
具体函数的实现思路拆解:
- ① 定义test_steps.txt,所有的测试步骤(关键字实现的)
- ② 框架程序要读test_steps.txt,所有的行放到一个列表中,列表中的每一个元素是文件中的一行
test_steps = [“open_browser||chrome\n”,“visit||https://www.baidu.com/\n”,…] - ③ 判断test_steps.txt列表中一共有多少元素,就知道有多少行,也就有多少个测试步骤
- ④ 使用for循环,有多少个测试步骤,就循环多少次
- ⑤ 使用strip去掉换行符\n,使用split做切割分别得到关键字和对应参数:
【情况1】:
有两个“||”,比如:“input||kw;;;ID||数据驱动测试\n”
step = “input||kw||数据驱动测试\n”.strip().split("||")
得到结果:[‘input’, ‘kw;;;ID’, ‘数据驱动测试’]
keyword = step[0]
element = step[1]
value = setp[2]
【情况2】:
有一个“||”,比如:“open_browser||chrome\n”
step = “open_browser||chrome\n”.strip().split("||")
得到结果:[‘open_browser’, ‘chrome’]
keyword = step[0]
element = None
value = setp[1]
【情况3】:
没有“||”,比如:“quit”
keyword = step[0]
- ⑥ 取到的几个值,最终要拼成一个函数调用的字符串:
command = ‘open_browser(“chrome”)’
command = ‘input(“kw;;;ID”,“数据驱动测试”)’
command = ‘quit()’
… - ⑦ 用Python中的eval函数来调用字符串表达式
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):
# 读取文件数据
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('测试步骤文件的数据为空,请检查后再测试')
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()函数执行command的代码
eval(command)
except:
flag = False
print('{} 测试用例执行失败'.format(command))
else:
print('{} 测试用例执行成功'.format(command))
三、混合模式驱动模式—HDT
1、核心原理
数据驱动+关键字驱动=混合驱动
就是数据驱动和关键字驱动的联合,既用到数据驱动模式也用到关键字驱动模式
2、适用场景
需要实现:关键字(函数名)和程序分离、数据(包括函数需要的参数)和程序分离的场景
测试人员只需在配置文件中维护好关键字和数据信息即可
3、案例演示
需求场景:用谷歌浏览器打开百度,输入不同的词进行搜索,再对搜索结果进行一一校验(测试数据和测试步骤都放在配置文件中)
测试数据文件:test_datas.txt
{"search_word":"数据驱动测试", "expect_word":"DDT"}
{"search_word":"关键字驱动测试", "expect_word":"51"}
{"search_word":"混合驱动测试", "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()
# 读取测试数据
def get_test_datas(test_data_file_path):
if not os.path.exists(test_data_file_path):
print('{}测试数据文件不存在,请确认!'.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()
# 有几行测试数据,就执行几次测试步骤
for test_data in test_datas:
'''{"search_word":"数据驱动测试", "expect_word":"ddt"} 是json串,把json串转换为字典类型'''
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()函数执行command的代码'''
eval(command)
except:
flag = False
print('{} 测试用例执行失败'.format(command))
else:
print('{} 测试用例执行成功'.format(command))