数据驱动不适合做 UI 自动化测试

在做自动化测试的时候,我们经常会听到 DDT 数据驱动测试(Data-Driven Testing)。

数据驱动测试又叫做表格驱动测试或者参数化驱动测试。它指的是进行自动化测试时,把所有测试数据单独放在一个表格或者是独立的模块儿当中,当运行测试脚本的时候,在一个通用的测试逻辑中分别填入每一组测试数据。

图片

数据驱动可以解决两个问题。一、避免硬编码和重复代码,二、独立维护测试数据的表格,不需要频繁修改和维护代码。

在流程驱动中,一般来说是一个测试用例会对应编写一个测试脚本或者函数。如果每个用例需要执行的步骤相同,就会出现大量的重复代码。而且当测试用例需要修改的时候,需要维护大量的代码函数。

图片

数据驱动可以有效解决这两个问题。它只需要编写一个通用的测试函数,然后通过参数化的手段将每一行测试数据分别带入函数作为参数,不仅节省了大量的重复代码,而且当测试用例需要修改的时候,也只需要修改对应的表格文件,而不需要去修改代码。

图片

数据驱动当然是一种好的自动化测试方式,但是有些人还是对它有不少误解。第一种误解,以为把测试数据存在 excel 当中,就是数据驱动。实际上,除了excel表格,csv文件、yaml 文件、json 文件和数据库都可以作为数据驱动的表类型,也可以直接用编程语言当中的数据类型存储。

第二种误解,有些人觉得数据驱动是万能的,只要做自动化测试一定要用数据驱动。实际上数据驱动的使用场景也是比较有限的,他只适合在所有测试数据共享同一套代码逻辑的测试场景下。

其中最典型的就是接口自动化测试。 接口自动化测试下,测试逻辑几乎都是一致的:获取请求的url地址、请求方法、请求参数,传入客户端中,获取服务器的响应数据。

@pytest.mark.parametrize('url, method, data', table_data)
def test_login_(url, method, data):
    resp = requests.request(method, url, json=data)

但是 ui 自动化测试,每个用例需要执行的动作和测试步骤都会有很大的区别。要实现数据驱动,只能通过把数据分组,每一组共享一个测试函数的方式。但是这种形式要同时维护多张数据表和多个函数,还不如采用硬编码,一个用例对应一个测试函数维护起来方便。

import pytest
# 维护多个 excel 表格
excel_1 = read_excel('1')
excel_2 = read_excel('2')
excel_3 = read_excel('3')
# 维护多个函数
@pytest.mark.parametrize('case', excel_1)
def test_login_group_1():
    pass
    
@pytest.mark.parametrize('case', excel_2)
def test_login_group_2():
    pass
    
@pytest.mark.parametrize('case', excel_3)
def test_login_group_3():
    pass

那么 ui 自动化测试适合使用什么方式呢?

第一种方式就是采用硬编码这种最直接的方式。 每个用例单独编写一个测试函数,测试数据可以通过变量保存在测试函数最开始的位置,可以避免到处找测试数据的尴尬。这种方式只是把之前维护数据表格的工作转移到维护测试函数上来,虽然会多写一些重复代码,但是维护起来也不是特别困难。

def test_login_1():
    username = ''
    password = ''
    login(username, password)
    
def test_login_2():
    username = 'error name'
    password = '123456'
    login(username, password)
    
def test_login_3():
    username = 'real name'
    password = 'correct password'
    login(username, password)

也可以结合数据驱动,采用数据分组编写测试函数的方式,但是不要独立去维护excel表格了,而是直接在测试代码里使用编程语言的数据类型存储分组数据,减少代码维护的成本。

import pytest
group_1 = [ ("", "")]
group_2 = [ ("error name", "")]
group_3 = [ ("correct name", "")]
@pytest.mark.parametrize('name, password', group_1)
def test_login_group_1(name, password):
    login(name, password)
    
@pytest.mark.parametrize('name, password', group_2)
def test_login_group_1(name, password):
    login(name, password)
    
@pytest.mark.parametrize('name, password', group_3)
def test_login_group_1(name, password):
    login(name, password)

第二种方式可以使用关键字驱动。 关键字驱动和数据驱动的区别在于:不仅隔离了数据,而且把需要执行的动作也以字符串的形式,存放到表格当中。和数据驱动相同的是,它还是只有一个通用的测试执行函数。当运行测试脚本时, 每条数据和动作会一起作为参数传入执行函数中。

我们来举一个实际的例子,首先创建一个yaml 文件存储一条用例的所有测试步骤、需要调用的操作和数据。

-
  action: "goto"
  params:
    url: "https://prepc.ketangpai.com/#/login"
-
  action: "type"
  params:
    locator: ['xpath', '//input[@type="text"]']
    data: 'looker53@sina.com'
-
  action: "type"
  params:
    locator: ['xpath', '//input[@type="password"]']
    data: 'admin123456'
-
  action: "click"
  params:
    locator: ['xpath', "//div[@class='margin-top']//button[@type='button']"]

然后编写一个通用的函数调用 yaml 中的动作和数据:

import yaml
fpath = r'C:\Users\muji\Desktop\ketangpai_web_testing\data\login_ok.yaml'
with open(fpath, encoding='utf8') as f:
    data = yaml.safe_load(f)
def test_keyword(driver):
    for step in data:
        method = getattr(driver, step['action'])
        method(**step['params'])

动作需要提前写在某个固定的模块或者类当中,然后通过 getattr 这种动态获取动作的操作从类中提取动作,再传入测试数据。比如上面的例子中,goto、type、click 动作都被保存在一个 Driver 的类中。

图片

数据驱动是一种很好的模式,在进行接口自动化测试这样统一步骤的场景下非常有用。再 ui 自动化测试时,用硬编码或者关键字驱动会更加合适。

参考资料: