作者:赵泽鑫|QE_LAB
数据驱动测试(Data-driven test)是一种软件测试方法,其中测试数据以表格或电子表格格式存储。数据驱动测试允许测试人员输入一个单一的测试脚本,可以对表格中的所有测试数据执行测试,并期望在同一表格中的测试输出。它也被称为表驱动测试或参数化测试。尤其是针对于自动化测试,DDT 几乎是所有自动化测试默认遵循的规范。当我们执行自动化测试的时候,都会遵照 DDT 的要求,尽量将测试中的可变数据抽象出来,变成单独的文件,这个文件的格式可以是 csv、xslx,也可以是 json、xml 或者是 database。
DDT 的数据一般包括支持自动化测试用例执行的测试数据,UI 测试使用的元素定位数据,和测试产生的测试结果。使用 DDT 的方式编写我们的测试脚本可以 1. 尽量保证 test case 和容易发生变化的数据解耦。这样当容易发生变化的数据变更后可以在统一的位置进行维护。同时脚本的实现方式发生变化的时候不影响用例本身的执行。2. 可以复用代码,避免重复冗余的代码。
DDT 究竟怎么落地到实际的测试中呢?举个,最简单的登陆注册页面,正确的用户名,错误的密码;正确的密码,错误的用户名;正确的用户名,正确的密码; 甚至输入用户名的格式和密码的格式不同都会产生不同的用例。那我们可以把这些不同的数据存在一个文件中,用变量的方式把数据传递到登陆的方法中,这样就完成了一个简单的 DDT 测试脚本。
为了能更加方便的使用 DDT 的方式编写测试,有很多工具来辅助我们更简单的实现 DDT。如果你的常用编程语言是 python,可以了解下 python 中的 selenium-ddt 库。里面的常用方法是@data、@ddt、@unpack、@file_data这四种装饰器。
import os
from ddt import ddt, data, unpack, file_data
import unittest
def get_data():
testdata = [{'name': 'tom', 'age': 20}, {'name': 'kite', 'age': 30}]
return testdata
@ddt
class MyTestCase(unittest.TestCase):
@data(1, 2, 3) //单组元组数据
def test1(self, value):
print(value)
@data((1, 2, 3), (4, 5, 6))
def test2(self, value):
print(value)
@data((1, 2, 3), (4, 5, 6)) //多组元组数据
@unpack // 加入unpack方法后使用两组数据执行该用例
def test3(self, value1, value2, value3):
print(value1, value2, value3)
@data([{'name': 'tom', 'age': 20}, {'name': 'kite', 'age': 30}]) //列表数据
def test4(self, value):
print(value)
@data({'name': 'tom', 'age': '20'}, {'name': 'kite', 'age': '30'})//字典数据
def test5(self, value):
print(value)
@data({'name': 'tom', 'age': '20'}, {'name': 'kite', 'age': '30'})//多组字典数据拆分
@unpack
def test6(self, name, age):
print(name, age)
testdata = [{'name': 'tom', 'age': 20}, {'name': 'kite', 'age': 30}]
# @data(*testdata)
@data(get_data()) //可以引用其他方法中的数据
def test7(self, value):
print(value)
@file_data(os.getcwd() + '/test.json')//读取文件应用文件中的数据
def test8(self, value2):
print(value2)
if __name__ == '__main__':
unittest.main()
代码比较简单但是可以说明问题,感兴趣的同学可以自己尝试运行一下,查看结果。可以看到 ddt 中支持多种多样数据结构的同时,也支持不同获取数据的来源,可以是定义在脚本中的数据,也可以是函数的返回数据,还可以读取文件中的数据。虽然在 pytest 支持参数化注解后就很少人使用 selenium-ddt 了,但是其中的设计思想还是值得参考。
下面是一个 pytest 参数化的。pytest 的参数化是通过@pytest.mark.parametrize 注解来实现的。@pytest.mark.parametrize 的内部是 ParametrizeMarkDecorator,ParametrizeMarkDecorator 的类中传入了 MarkDecorator 装饰器,这个装饰器被调用后,会将标记附加在类中,自动应用到类中找到的所有用例里面。这就是为什么使用注解后的用例可以全部被执行的原因。而读取这些数据解析出来,是因为 ParametrizeMarkDecorator 的实现是一个 Iterable,当传入的 data 是一个可迭代对象的时候,就可以将 data 中的数据 unpack 出来。
login_data = [
('test001', 'test001@qq.com', '123456', '123456', '666', '验证码不正确'),
('test200', 'test009@qq.com', '123456', '123456', '111', '注册成功,点击确定进行登录。'),
]
@pytest.mark.parametrize('username,email,pwd,expected', login_data)
def test1_register(self, username, email, pwd, expected):
self.driver.find_element(By.NAME, 'username').send_keys(username)
self.driver.find_element(By.NAME, 'email').send_keys(email)
self.driver.find_element(By.NAME, 'pwd').send_keys(pwd)
self.driver.find_element(By.NAME, 'comfirmed').click()
WebDriverWait(self.driver, 5).until(EC.alert_is_present())
alert = self.driver.switch_to.alert
assert alert.text == expected
alert.accept()
DDT 是一种编写自动化测试的规范或者说是一个框架,用文件的形式存储测试数据,达到数据和测试脚本物理上的隔离,从而达到更好维护用例和更 make sense 的结果。大家在实践 DDT 或者学习 DDT 过程中遇到过哪些问题或者有什么更好的实践,欢迎找我讨论!