在自动化测试过程中,经常面临测试数据的维护和管理等问题。如对登录进行测试,每次流程都是一样,只是测试数据不一样,这种情况在写测试用例时,需要创建多组不同的测试数据来检查登录功能的正确性。这时,就可以使用数据驱动测试。
DDT
DDT(Data-Driven Tests)数据驱动测试:因测试数据的改变而驱动自动化测试的执行,最终引起测试结果的改变。通过数据驱动测试的方法,每一组输入数据都对应一组测试用例,可以验证多组数据场景
安装DDT
Github地址:DDT
pip install ddt
DDT在自动化测试中的应用
在进行数据驱动测试实战中,需要在测试类上使用@ddt.ddt
装饰器,在测试用例上使用@ddt.data
装饰器。@ddt.data装饰器可以把参数作为测试数据,参数可以是单个值,列表、元组或字典。对于列表和元组,需要使用@ddt.unpack装饰器吧元组合列表解析成多个参数。
示例
import ddt
import unittest
data = [
{"name": "ddt"},
{"age": 5},
{"address": "know"}
]
@ddt.ddt
class TestSample(unittest.TestCase):
def setUp(self):
print("Test Case Begining........")
@ddt.data(*data)
def test_datadrivendata(self, data):
print("数据驱动测试:", data)
def tearDown(self):
print("Test Case Ending...................")
if __name__ == "__main__":
unittest.main()
执行结果:
Testing started at 下午 17:48 ...
D:\ENV_DIRECTOR\Envs\py3_heima\Scripts\python.exe "F:\Pycharm\PyCharm 2017.3.3\helpers\pycharm\_jb_unittest_runner.py" --path D:/zhenghou/python_learning/test_ddt/test_ddt1.py
Launching unittests with arguments python -m unittest D:/zhenghou/python_learning/test_ddt/test_ddt1.py in D:\zhenghou\python_learning\test_ddt
Test Case Begining........
数据驱动测试: {'name': 'ddt'}
Test Case Ending...................
Test Case Begining........
数据驱动测试: {'age': 5}
Test Case Ending...................
Test Case Begining........
数据驱动测试: {'address': 'know'}
Test Case Ending...................
Ran 3 tests in 0.002s
OK
不使用@ddt.unpack说明是两组测试数据,将data内的每组数据跟别作为参数传入@ddt.data()方法中,从而实现数据驱动测试。
使用@ddt.unpack装饰器解析列表或元组为多组参数。
import ddt
import unittest
data = [
["ddt", 5],
['robotframework', 4],
['unittest', 10]
]
@ddt.ddt
class TestSample(unittest.TestCase):
def setUp(self):
print("Test Case Begining........")
@ddt.data(*data)
@ddt.unpack
def test_datadrivendata(self, username, value):
print("数据驱动测试--username:", username)
print("数据驱动测试--value:", value)
def tearDown(self):
print("Test Case Ending...................")
if __name__ == "__main__":
unittest.main()
执行结果
Testing started at 下午 17:55 ...
D:\ENV_DIRECTOR\Envs\py3_heima\Scripts\python.exe "F:\Pycharm\PyCharm 2017.3.3\helpers\pycharm\_jb_unittest_runner.py" --path D:/zhenghou/python_learning/test_ddt/test_ddt1.py
Launching unittests with arguments python -m unittest D:/zhenghou/python_learning/test_ddt/test_ddt1.py in D:\zhenghou\python_learning\test_ddt
Test Case Begining........
数据驱动测试--username: ddt
数据驱动测试--value: 5
Test Case Ending...................
Test Case Begining........
数据驱动测试--username: robotframework
数据驱动测试--value: 4
Test Case Ending...................
Test Case Begining........
数据驱动测试--username: unittest
数据驱动测试--value: 10
Test Case Ending...................
Ran 3 tests in 0.005s
OK
Process finished with exit code 0
以百度搜索为例,验证输入后校验title,在readData()方法中定义3组数据,分别是执行那个case,输入内容,和失败后提示信息。@unpack用于将readData()方法中的列表等分解为多组测试数据。
import time
import unittest
import ddt
from selenium import webdriver
def readData():
# 列表
# data = [
# ["case1", "selenium", '校验失败'],
# ["case2", "ddt", "not true"],
# ['case3', "unittest", "hahah, you False"]
# ]
# 元组
# data = (
# ("case1", "selenium", '校验失败'),
# ("case2", "ddt", "not true"),
# ('case3', "unittest", "hahah, you False")
# )
# 字典
data = (
{"case": "case1", "search_key": "selenium", "text": "校验失败"},
{"case": "case2", "search_key": "ddt", "text": "not true"},
{"case": "case3", "search_key": "unittest", "text": "hahah, you False"},
)
return data
@ddt.ddt
class TestBadiduSearch(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.driver = webdriver.Chrome()
def baidu_search(self, search_key):
self.driver.get("http://www.baidu.com")
self.driver.find_element_by_id("kw").send_keys(search_key)
self.driver.find_element_by_id("su").click()
time.sleep(3)
@ddt.data(*readData())
@ddt.unpack
def test_search1(self, case, search_key, text):
print("正在执行" + case + "==============================")
self.baidu_search(search_key)
self.assertEqual(self.driver.title, search_key + "_百度搜索", text)
@classmethod
def tearDownClass(cls):
cls.driver.quit()
if __name__ == '__main__':
unittest.main()
此外DDT还支持数据文件的参数化。它封装了数据文件的读取,让测试人员更专注于数据文件中的内容,以及在测试用例中的使用,而不需要关心数据文件是如何读取进来的。
首先,创建ddt_data_file.json文件
{
"case1": {"search_key": "selenium", "case": "case1"},
"case2": {"search_key": "ddt", "case": "case2"},
"case3": {"search_key": "unittest", "case": "case3"}
}
使用json文件中的数据
@ddt.file_data("ddt_data_file.json")
def test_datadrivendata(self,search_key, case):
print("数据驱动测试------", search_key)
print("数据驱动测试------test", case)
DDT还支持yaml格式的数据文件,
Excel
在实际自动化测试任务中,很多情况下会将测试脚本中的测试数据和测试用例分开管理,一般会将测试数据单独存放在Excel文件中。
python操作excel主要用到xlrd和xlwt这两个库,即xlrd是读excel,xlwt是写excel的库。
安装
pip install xlrd
xlrd
使用
打开excel文件并创建对象存储
import xlrd
book = xlrd.open_workbook('test.xlsx')
获取一个工作表
- 打印所有的sheet名
book = xlrd.open_workbook('test.xlsx')
print(book.sheet_names())
- 通过索引顺序获取
# 第一种通过索引获取
sheet1 = book.sheets()[0]
# 第二种通过索引获取
sheet1 = book.sheet_by_index(0)
- 通过sheet名获取
sheet1 = book.sheet_by_name("Sheet1")
获取整行和整列的值(数组)
# 行
sheet1.row_values(0)
# 列
sheet1.col_values(i)
获取行数和列数
# 行数
sheet.nrows
# 列数
sheet.ncols
循环行列表的数据
nrows = sheet1.nrows
for i in range(nrows):
print(sheet1.row_values(i))
单元格
单元格的操作 ·
# table.cell(rowx,colx) #返回单元格对象
sheet1.cell(1, 0).value
# table.cell_type(rowx,colx) #返回单元格中的数据类型
# 类型 0 empty,1 string, 2 number, 3 date, 4 boolean, 5 error
sheet.cell_type(1, 1)
table.cell_value(rowx,colx) #返回单元格中的数据
table.cell_xf_index(rowx, colx) # 暂时还没有搞懂
Excel 整合 DDT
import time
import ddt
import xlrd
import unittest
from selenium import webdriver
from selenium.webdriver.support import expected_conditions as EC
def readData():
book = xlrd.open_workbook("test.xlsx")
sheet = book.sheet_by_index(0) # 获取第一个sheet表
newRows = []
for row in range(1, sheet.nrows):
newRows.append(sheet.row_values(row, 0, sheet.ncols))
return newRows
@ddt.ddt
class TestBaiduSearch(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.driver = webdriver.Chrome()
def baiduSearch(self, search_text):
self.driver.get("http://www.baidu.com")
self.driver.find_element_by_id("kw").send_keys(search_text)
self.driver.find_element_by_id("su").click()
time.sleep(3)
@ddt.data(*readData())
@ddt.unpack
def test_case(self, search_text, assert_text):
self.baiduSearch(search_text)
self.assertEqual(self.driver.title, assert_text)
@classmethod
def tearDownClass(cls):
cls.driver.quit()
if __name__ == "__main__":
unittest.main()
csv
CSV (Comma Separated Vaules) 格式是电子表格和数据库中最常见的输入、输出文件格式。
csv 模块实现了 CSV 格式表单数据的读写。其提供了诸如“以兼容 Excel 的方式输出数据文件”或“读取 Excel 程序输出的数据文件”的功能,程序员无需知道 Excel 所采用 CSV 格式的细节。此模块同样可以用于定义其他应用程序可用的 CSV 格式或定义特定需求的 CSV 格式。
csv 模块中的 reader 类和 writer 类可用于读写序列化的数据。也可使用 DictReader 类和 DictWriter 类以字典的形式读写数据。
建立csv
文件如下:
username,password,assert_text
admin1,psd1,ggg
admin2,psd2,hhh
admin3,psd3,jjj
Reader()
class csv.DictWriter(f, fieldnames, restval='', extrasaction='raise', dialect='excel', *args, **kwds)
创建一个对象,该对象在操作上类似常规 writer,但会将字典映射到输出行。 fieldnames 参数是由键组成的 序列,它指定字典中值的顺序,这些值会按指定顺序传递给 writerow() 方法并写入文件 f。 如果字典缺少 fieldnames 中的键,则可选参数 restval 用于指定要写入的值。 如果传递给 writerow() 方法的字典的某些键在 fieldnames 中找不到,则可选参数 extrasaction 用于指定要执行的操作。 如果将其设置为默认值 ‘raise’,则会引发 ValueError。 如果将其设置为 ‘ignore’,则字典中的其他键值将被忽略。 所有其他可选或关键字参数都传递给底层的 writer 实例。
import csv
with open("data.csv", 'r') as f:
data = csv.reader(f)
for line in data:
print(line)
# 执行结果
"""
['username', 'password', 'assert_text']
['admin1', 'psd1', 'ggg']
['admin2', 'psd2', 'hhh']
['admin3', 'psd3', 'jjj']
"""
##DictReader()
import csv
with open("data.csv", 'r') as f:
# data = csv.reader(f)
data = csv.DictReader(f)
for line in data:
print(dict(line))
# 执行结果
"""
{'username': 'admin1', 'password': 'psd1', 'assert_text': 'ggg'}
{'username': 'admin2', 'password': 'psd2', 'assert_text': 'hhh'}
{'username': 'admin3', 'password': 'psd3', 'assert_text': 'jjj'}
"""
codecs
模块
在数据文件中不可避免会使用中文,codecs是python的标准的模块编码和解码器。
因为文件中我们一般会在第一行定义字段名,在读取时需要跳过,itertools提供了用于操作迭代对象的函数,即islice()
函数,它可以返回一个迭代器第一个参数指定迭代对象,第二个参数指定开始迭代的位置,第三个参数表示结束位。
import csv
import codecs
import itertools
# csv内容如下
"""
username,password,assert_text
admin1,psd1,ggg
admin2,psd2,hhh
admin3,psd3,中文
"""
with codecs.open("data.csv", 'r', 'utf_8_sig') as f:
data = csv.reader(f)
for line in itertools.islice(data, 1, None): # 跳过头
print(line)
"""
['admin1', 'psd1', 'ggg']
['admin2', 'psd2', 'hhh']
['admin3', 'psd3', '中文']
"""
YAML
YAML是一种直观的能够被计算机识别的数据序列化格式,容易阅读,并且容易和脚本语言交互。YAML类似于XML,但是语法较XML简单的多:JSON,YAML可以写成规范化的配置文件。此外,不管做Web自动化测试还是接口自动化测试,都可以使用YAML来管理测试数据。
安装
pip install pyyaml
YAML文档可参考:YAML Documentation
YAML
建立data.yaml
文件
userNull:
username: ""
password: ""
assertText: ""
passNull:
username1: "admin"
password1: "12345"
assertText1: "请输入账号密码"
读取yaml文件
from yaml import load, FullLoader
def readYaml():
"""获取所有的yaml数据"""
f = open("data.yaml", 'r', encoding="utf-8")
data = load(f, Loader=FullLoader)
f.close()
return data
if __name__ == '__main__':
print(readYaml())
Excel结合DDT
import ddt
import yaml
import unittest
import time
from selenium import webdriver
@ddt.ddt
class TestBaidu(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.driver = webdriver.Chrome()
def baiduSearch(self, search_text):
self.driver.get("http://www.baidu.com")
self.driver.find_element_by_id("kw").send_keys(search_text)
self.driver.find_element_by_id("su").click()
time.sleep(3)
@ddt.file_data("ddt_data.yaml") # 中文乱码
@ddt.unpack
def test_case(self, case):
print("正在测试----" + case[0]["search_text"] + "------------")
search_text = case[0]['search_text']
assert_text = case[1]['assertText']
self.baiduSearch(search_text)
self.assertEqual(self.driver.title, assert_text)
@classmethod
def tearDownClass(cls):
cls.driver.quit()
if __name__ == "__main__":
unittest.main()
parameterized
参数化,是对unittest单元测试框架的参数化扩展。它可以结合unittest单元测试框架进行一些参数化策略的应用。
安装
pip install parameterized
parameterized在自动化测试中应用
import unittest
import time
from selenium import webdriver
from parameterized import parameterized
class TestBaiduSearch(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.driver = webdriver.Chrome()
def baidu_search(self, url, search_text):
self.driver.get(url)
self.driver.find_element_by_id("kw").send_keys(search_text)
self.driver.find_element_by_id("su").click()
time.sleep(3)
@parameterized.expand([
("http://www.baidu.com", "selenium", "selenium_百度搜索"),
("http://www.baidu.com", "ddt", "ddt_百度搜索"),
("http://www.baidu.com", "parameterized", "selenium_百度搜索")
])
def test_baidu(self, url, search_text, assert_text):
self.baidu_search(url, search_text)
self.assertEqual(self.driver.title, assert_text)
@classmethod
def tearDownClass(cls):
cls.driver.quit()
if __name__ == "__main__":
unittest.main()
@parameterized.expand()
方法用来存放多组测试数据,每组测试数据都会作为实参传递给测试用例中的url、search_text和assert_text。