python实现自动化测试框架如何进行数据参数化?这个包可以了解下
off999 2024-11-03 14:15 20 浏览 0 评论
1.数据参数化介绍
只要你是负责编写自动化测试脚本的,数据参数化这个思想你就肯定会用 ,数据参数化的工具你肯定的懂一些 ,因为它能大大的提高我们自动化脚本编写效率 。
1.1什么是数据参数化
所谓的数据参数化 ,是指所执行的测试用例步骤相同、而数据不同 ,每次运行用例只变化的是数据 ,于是将这些数据专门放在一起进行批量循环运行 ,从而完成测试用例执行的目的 。
以登录功能为例 ,若一个登录功能每次操作的步骤是 :
- 输入用户名
- 输入密码
- 点击登录按钮 。
但是,因为每次输入的数据不同,导致生成的测试用例就不同了 ,同样还是这个登录功能,加上数据就变为以下的用例了 。
- case1 : 输入正确的用户名 ,输入正确的密码 ,点击登录
- case2 : 输入正确的用户,输入错误的密码,点击登录
- case3 :输入正确的用户名,输入空的密码,点击登录
- casen : ...
可以看到 ,在这些用例中,每条用例最大的不同是什么呢 ?其实就是数据不同 。但是由于数据不同,从而生成了多条测试用例 ,在功能测试中,这些用例是需要分别写、分别执行 。
1.2.为什么要进行数据参数化 ?
在功能测试中,即使是相同的步骤 ,只是数据不同 ,我们亦然也要尽量分开编写每一条用例 ,比如像上面的编写方式 ,因为这些编写它的易读性更好 ,功能测试设计测试用例和执行用例往往不是一个人 ,所以用例编写的易读性是就是一个很重要的因素 。
但是如果将上面的用例进行自动化实现 ,虽然按照一条用例对应一个方法是一种很清晰的思路 ,但是它的最大问题就是代码冗余 ,当一个功能中步骤相同,只是数据不同时,你的数据越多,代码冗余度就越高 。你会发现每个测试方法中的代码就会是相同的 。
像代码冗余这种问题,在编写自动化时是必须要考虑的一个问题,因为随着代码量越多 ,冗余度越高、越难维护 。
以下就是是通过正常方式实现登录的自动化脚本 :
import unittest
from package_unittest.login import login
class TestLogin(unittest.TestCase):
# case1 : 输入正确的用户名和正确的密码进行登录
def test_login_success(self):
expect_reslut = 0
actual_result = login('admin','123456').get('code')
self.assertEqual(expect_reslut,actual_result)
# case2 : 输入正确的用户名和错误的密码进行登录
def test_password_is_wrong(self):
expect_reslut = 3
actual_result = login('admin', '1234567').get('code')
self.assertEqual(expect_reslut, actual_result)
# case3 : 输入正确的用户名和空的密码进行登录
def test_password_is_null(self):
expect_reslut = 2
actual_result = login('admin', '').get('code')
self.assertEqual(expect_reslut, actual_result)
可以看到,三条用例对应三个测试方法,虽然清晰 ,代码每个方法中的代码几乎是相同的。
那如果用参数化实现的代码是什么呢 ? 可以看下面的这段代码 :
class TestLogin(unittest.TestCase):
@parameterized.expand(cases)
def test_login(self,expect_result,username,password):
actual_result = login(username,password).get('code')
self.assertEqual(expect_result,actual_result)
以上代码只有一条用例 ,不管这个功能有几条都能执行 。
通过上面两种形式的比较可以看出 :为什么要进行数据参数化呢 ?其实就是降低代码冗余、提高代码复用度 ,将主要编写测试用例的时间转化为编写测试数据上来 。
1.3.如何进行数据参数化
在代码中实现数据参数化都需要借助于外部工具 ,比如专门用于unittest的ddt , 既支持unittest、也支持pytest的parameterized ,专门在pytest中使用的fixture.params .
参数化工具 | 支持测试框架 | 备注 |
ddt | unittest | 第三方包,需要下载安装 |
parameterized | nose,unittest,pytest | 第三方包,需要下载安装 |
@pytest.mark.parametrize | pytest | 本身属于pytest中的功能 |
@pytest.fixture(params=[]) | pytest | 本身属于pytest中的功能 |
以上实现数据参数化的工具有两个共同点:
- 都能实现数据参数化
- 都时装饰器来作用于测试用例脚本 。
2.模块介绍
1.下载安装 :
# 下载
pip install parameterized
# 验证 :
pip show parameterized
2.导包
# 直接导入parameterized类
from parameterized import parameterized
3.官网示例
@parameterized 和 @parameterized.expand 装饰器接受列表 或元组或参数(...)的可迭代对象,或返回列表或 可迭代:
from parameterized import parameterized, param
# A list of tuples
@parameterized([
(2, 3, 5),
(3, 5, 8),
])
def test_add(a, b, expected):
assert_equal(a + b, expected)
# A list of params
@parameterized([
param("10", 10),
param("10", 16, base=16),
])
def test_int(str_val, expected, base=10):
assert_equal(int(str_val, base=base), expected)
# An iterable of params
@parameterized(
param.explicit(*json.loads(line))
for line in open("testcases.jsons")
)
def test_from_json_file(...):
...
# A callable which returns a list of tuples
def load_test_cases():
return [
("test1", ),
("test2", ),
]
@parameterized(load_test_cases)
def test_from_function(name):
...
请注意,使用迭代器或生成器时,将加载所有项 在测试运行开始之前放入内存(我们显式执行此操作以确保 生成器在多进程或多线程中只耗尽一次 测试环境)。
@parameterized装饰器可以使用测试类方法,并且可以独立使用 功能:
from parameterized import parameterized
class AddTest(object):
@parameterized([
(2, 3, 5),
])
def test_add(self, a, b, expected):
assert_equal(a + b, expected)
@parameterized([
(2, 3, 5),
])
def test_add(a, b, expected):
assert_equal(a + b, expected)
@parameterized.expand可用于生成测试方法 无法使用测试生成器的情况(例如,当测试 类是单元测试的一个子类。测试用例):
import unittest
from parameterized import parameterized
class AddTestCase(unittest.TestCase):
@parameterized.expand([
("2 and 3", 2, 3, 5),
("3 and 5", 3, 5, 8),
])
def test_add(self, _, a, b, expected):
assert_equal(a + b, expected)
将创建测试用例:
$ nosetests example.py
test_add_0_2_and_3 (example.AddTestCase) ... ok
test_add_1_3_and_5 (example.AddTestCase) ... ok
----------------------------------------------------------------------
Ran 2 tests in 0.001s
OK
请注意,@parameterized.expand 的工作原理是在测试上创建新方法 .class。如果第一个参数是字符串,则该字符串将添加到末尾 的方法名称。例如,上面的测试用例将生成方法test_add_0_2_and_3和test_add_1_3_and_5。
@parameterized.expand 生成的测试用例的名称可以是 使用 name_func 关键字参数进行自定义。该值应 是一个接受三个参数的函数:testcase_func、param_num、 和参数,它应该返回测试用例的名称。testcase_func是要测试的功能,param_num将是 参数列表中测试用例参数的索引,参数(参数的实例)将是将使用的参数。
import unittest
from parameterized import parameterized
def custom_name_func(testcase_func, param_num, param):
return "%s_%s" %(
testcase_func.__name__,
parameterized.to_safe_name("_".join(str(x) for x in param.args)),
)
class AddTestCase(unittest.TestCase):
@parameterized.expand([
(2, 3, 5),
(2, 3, 5),
], name_func=custom_name_func)
def test_add(self, a, b, expected):
assert_equal(a + b, expected)
将创建测试用例:
$ nosetests example.py
test_add_1_2_3 (example.AddTestCase) ... ok
test_add_2_3_5 (example.AddTestCase) ... ok
----------------------------------------------------------------------
Ran 2 tests in 0.001s
OK
param(...) 帮助程序类存储一个特定测试的参数 箱。它可用于将关键字参数传递给测试用例:
from parameterized import parameterized, param
@parameterized([
param("10", 10),
param("10", 16, base=16),
])
def test_int(str_val, expected, base=10):
assert_equal(int(str_val, base=base), expected)
如果测试用例具有文档字符串,则该测试用例的参数将为 附加到文档字符串的第一行。可以控制此行为 doc_func参数:
from parameterized import parameterized
@parameterized([
(1, 2, 3),
(4, 5, 9),
])
def test_add(a, b, expected):
""" Test addition. """
assert_equal(a + b, expected)
def my_doc_func(func, num, param):
return "%s: %s with %s" %(num, func.__name__, param)
@parameterized([
(5, 4, 1),
(9, 6, 3),
], doc_func=my_doc_func)
def test_subtraction(a, b, expected):
assert_equal(a - b, expected)
$ nosetests example.py
Test addition. [with a=1, b=2, expected=3] ... ok
Test addition. [with a=4, b=5, expected=9] ... ok
0: test_subtraction with param(*(5, 4, 1)) ... ok
1: test_subtraction with param(*(9, 6, 3)) ... ok
----------------------------------------------------------------------
Ran 4 tests in 0.001s
OK
最后@parameterized_class参数化整个类,使用 属性列表或将应用于 .class:
from yourapp.models import User
from parameterized import parameterized_class
@parameterized_class([
{ "username": "user_1", "access_level": 1 },
{ "username": "user_2", "access_level": 2, "expected_status_code": 404 },
])
class TestUserAccessLevel(TestCase):
expected_status_code = 200
def setUp(self):
self.client.force_login(User.objects.get(username=self.username)[0])
def test_url_a(self):
response = self.client.get('/url')
self.assertEqual(response.status_code, self.expected_status_code)
def tearDown(self):
self.client.logout()
@parameterized_class(("username", "access_level", "expected_status_code"), [
("user_1", 1, 200),
("user_2", 2, 404)
])
class TestUserAccessLevel(TestCase):
def setUp(self):
self.client.force_login(User.objects.get(username=self.username)[0])
def test_url_a(self):
response = self.client.get("/url")
self.assertEqual(response.status_code, self.expected_status_code)
def tearDown(self):
self.client.logout()
@parameterized_class装饰器接受class_name_func论点, 它控制由 @parameterized_class 生成的参数化类的名称:
from parameterized import parameterized, parameterized_class
def get_class_name(cls, num, params_dict):
# By default the generated class named includes either the "name"
# parameter (if present), or the first string value. This example shows
# multiple parameters being included in the generated class name:
return "%s_%s_%s%s" %(
cls.__name__,
num,
parameterized.to_safe_name(params_dict['a']),
parameterized.to_safe_name(params_dict['b']),
)
@parameterized_class([
{ "a": "hello", "b": " world!", "expected": "hello world!" },
{ "a": "say ", "b": " cheese :)", "expected": "say cheese :)" },
], class_name_func=get_class_name)
class TestConcatenation(TestCase):
def test_concat(self):
self.assertEqual(self.a + self.b, self.expected)
$ nosetests -v test_math.py
test_concat (test_concat.TestConcatenation_0_hello_world_) ... ok
test_concat (test_concat.TestConcatenation_0_say_cheese__) ... ok
使用单个参数
如果测试函数只接受一个参数并且该值不可迭代, 然后可以提供值列表,而无需将每个值包装在 元:
@parameterized([1, 2, 3])
def test_greater_than_zero(value):
assert value > 0
但请注意,如果单个参数是可迭代的(例如列表或 元组),那么它必须包装在元组、列表或 param(...) 装饰器中:
@parameterized([
([1, 2, 3], ),
([3, 3], ),
([6], ),
])
def test_sums_to_6(numbers):
assert sum(numbers) == 6
虽然看似以上功能支持的挺多 ,但其实真正用的不多 ,因为它跟框架有很大关系的 。具体说明下 :
总结:
- 它支持nose是最好的 . 如果你的自动化中使用nose,那么以上功能基本都能用到 。
- 如果你用的测试框架是unittest ,你只能用到它的expand()这个函数 ,不过有这个函数也就够了 。
- 如果你用的测试框架是pytest , 它支持了Pytest3的版本,再高版本的就不支持了,同时pytest也有自己的参数化工具,一般也不用它了。
3.项目实践
通过数据参数胡重新编写登录测试用例 ,将以前yaml中的登录用例数据转化为paramterized的数据格式 ,它的数据格式要求为:[(),(),()] . 所以,编写测试用例的数据就变为了以下的代码 。
# 将登录数据转化为paramterize所识别的格式。
def get_data():
yaml_path = get_file_path('login.yaml') # 获取login.yaml的全路径
result = read_yaml(yaml_path) # 转化为python对象
login_data = result.get('login') # 获取字典中login的值
logger.debug("登录结果:{}".format(login_data))
return (login_data) # 获取字典中login的值
@allure.epic("vshop")
@allure.story("登录")
class TestLogin(unittest.TestCase):
# case1 : 测试登录功能
@parameterized.expand(get_data())
def test_login(self,case_name,username,password,code,message):
logger.info("从参数化获取的数据:{}|{}|{}|{}|{}".format(case_name,username,password,code,message))
with allure.step("执行用例:{},输入用户名:{},输入密码:{}".format(case_name,username,password)):
login_result = login(username,password)
self.assertEqual(code, login_result.get('errno'))
self.assertEqual(message, login_result.get('errmsg'))
这样的话,我们只编写了一条测试用例 ,但是在测试数据中有几条数据 ,都可以正常运行 。
4.项目总结
至此,我们已经实现了五步了 ,分别是 :
第一 、如何编写一个接口自动化框架 ,在第一篇博文中介绍了 。https://www.toutiao.com/item/7223778665283404323/
第二、如何使用unittest编写测试用例 ,已经在第二篇博文中介绍了 。https://www.toutiao.com/item/7225986414469825024/
第三、如何使用requests实现接口请求 ,并和测试用例如何对接 ,已经在第三篇博文中介绍了。https://www.toutiao.com/item/7231485629643997748/
第四、如何使用yaml编写测试数据 ,已经在第四篇博文中介绍了 。https://www.toutiao.com/item/7236369710286733861/
第五,如何使用allure生成测试报告,已经在第五篇博文中介绍了 。https://www.toutiao.com/item/7243783682144944697/
第六 ,如何使用loguru记录日志 ,已经在第六篇博文中介绍了 。https://www.toutiao.com/item/7253833815246815796/
第七,如何使用pymysql连接数据库,已经在第七篇博文中介绍了 。https://www.toutiao.com/item/7256573953278214668/
第八,如何进行数据参数化 ,也就是本篇博文了 。
相关推荐
- 大文件传不动?WinRAR/7-Zip 入门到高手,这 5 个技巧让你效率翻倍
-
“这200张照片怎么传给女儿?微信发不了,邮箱附件又超限……”62岁的张阿姨对着电脑犯愁时,儿子只用了3分钟就把照片压缩成一个文件,还教她:“以后用压缩软件,比打包行李还方便!”职场人更懂这...
- 电脑解压缩软件推荐——7-Zip:免费、高效、简洁的文件管理神器
-
在日常工作中,我们经常需要处理压缩文件。无论是下载软件包、接收文件,还是存储大量数据,压缩和解压缩文件都成为了我们日常操作的一部分。而说到压缩解压软件,7-Zip绝对是一个不可忽视的名字。今天,我就来...
- 设置了加密密码zip文件要如何打开?这几个方法可以试试~
-
Zip是一种常见的压缩格式文件,文件还可以设置密码保护。那设置了密码的Zip文件要如何打开呢?不清楚的小伙伴一起来看看吧。当我们知道密码想要打开带密码的Zip文件,我们需要用到适用于Zip格式的解压缩...
- 大文件想要传输成功,怎么把ZIP文件分卷压缩
-
不知道各位小伙伴有没有这样的烦恼,发送很大很大的压缩包会受到限制,为此,想要在压缩过程中将文件拆分为几个压缩包并且同时为所有压缩包设置加密应该如何设置?方法一:使用7-Zip免费且强大的文件管理工具7...
- 高效处理 RAR 分卷压缩包:合并解压操作全攻略
-
在文件传输和存储过程中,当遇到大文件时,我们常常会使用分卷压缩的方式将其拆分成多个较小的压缩包,方便存储和传输。RAR作为一种常见的压缩格式,分卷压缩包的使用频率也很高。但很多人在拿到RAR分卷...
- 2个方法教你如何删除ZIP压缩包密码
-
zip压缩包设置了加密密码,每次解压文件都需要输入密码才能够顺利解压出文件,当压缩包文件不再需要加密的时候,大家肯定想删除压缩包密码,或是忘记了压缩包密码,想要通过删除操作将压缩包密码删除,就能够顺利...
- 速转!漏洞预警丨压缩软件Winrar目录穿越漏洞
-
WinRAR是一款功能强大的压缩包管理器,它是档案工具RAR在Windows环境下的图形界面。该软件可用于备份数据,缩减电子邮件附件的大小,解压缩从Internet上下载的RAR、ZIP及其它类...
- 文件解压方法和工具分享_文件解压工具下载
-
压缩文件减少文件大小,降低文件失效的概率,总得来说好处很多。所以很多文件我们下载下来都是压缩软件,很多小伙伴不知道怎么解压,或者不知道什么工具更好,所以今天做了文件解压方法和工具的分享给大家。一、解压...
- [python]《Python编程快速上手:让繁琐工作自动化》学习笔记3
-
1.组织文件笔记(第9章)(代码下载)1.1文件与文件路径通过importshutil调用shutil模块操作目录,shutil模块能够在Python程序中实现文件复制、移动、改名和删除;同时...
- Python内置tarfile模块:读写 tar 归档文件详解
-
一、学习目标1.1学习目标掌握Python内置模块tarfile的核心功能,包括:理解tar归档文件的原理与常见压缩格式(gzip/bz2/lzma)掌握tar文件的读写操作(创建、解压、查看、过滤...
- 使用python展开tar包_python拓展
-
类Unix的系统,打包文件经常使用的就是tar包,结合zip工具,可以方便的打包并解压。在python的标准库里面有tarfile库,可以方便实现生成了展开tar包。使用这个库最大的好处,可能就在于不...
- 银狐钓鱼再升级:白文件脚本化实现GO语言后门持久驻留
-
近期,火绒威胁情报中心监测到一批相对更为活跃的“银狐”系列变种木马。火绒安全工程师第一时间获取样本并进行分析。分析发现,该样本通过阿里云存储桶下发恶意文件,采用AppDomainManager进行白利...
- ZIP文件怎么打开?2个简单方法教你轻松搞定!
-
在日常工作和生活中,我们经常会遇到各种压缩文件,其中最常见的格式之一就是ZIP。ZIP文件通过压缩数据来减少文件大小,方便我们进行存储和传输。然而,对于初学者来说,如何打开ZIP文件可能会成为一个小小...
- Ubuntu—解压多个zip压缩文件.zip .z01 .z02
-
方法将所有zip文件放在同一目录中:zip_file.z01,zip_file.z02,zip_file.z03,...,zip_file.zip。在Zip3.0版本及以上,使用下列命令:将所有zi...
- 如何使用7-Zip对文件进行加密压缩
-
7-Zip是一款开源的文件归档工具,支持多种压缩格式,并提供了对压缩文件进行加密的功能。使用7-Zip可以轻松创建和解压.7z、.zip等格式的压缩文件,并且可以通过设置密码来保护压缩包中的...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- python计时 (73)
- python安装路径 (56)
- python类型转换 (93)
- python进度条 (67)
- python吧 (67)
- python的for循环 (65)
- python格式化字符串 (61)
- python静态方法 (57)
- python列表切片 (59)
- python面向对象编程 (60)
- python 代码加密 (65)
- python串口编程 (77)
- python封装 (57)
- python写入txt (66)
- python读取文件夹下所有文件 (59)
- python操作mysql数据库 (66)
- python获取列表的长度 (64)
- python接口 (63)
- python调用函数 (57)
- python多态 (60)
- python匿名函数 (59)
- python打印九九乘法表 (65)
- python赋值 (62)
- python异常 (69)
- python元祖 (57)