自动化测试之数据驱动与关键字驱动

一、概述

数据驱动的自动化测试

从数据文件读取输入数据,通过变量的参数化,将测试数据传入测试脚本,不同的数据文件对应不同的测试用例。在这种模式下数据和脚本分离,脚本的利用率、可维护性大大提高,数据的覆盖率也较高,但受界面变化的影响仍然很大。

关键字驱动的自动化测试

关键字驱动测试是数据驱动测试的一种改进类型,它将测试逻辑按照关键字进行分解,形成数据文件,关键字对应封装的业务逻辑。

主要关键字包括三类:被操作对象(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))

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容