自动化测试如何进行参数化?使用什么工具进行参数化?一文详解parameterized的使用以及如何在自动化中使用。

本文主要是介绍自动化测试如何进行参数化?使用什么工具进行参数化?一文详解parameterized的使用以及如何在自动化中使用。,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1.数据参数化介绍

只要你是负责编写自动化测试脚本的,数据参数化这个思想你就肯定会用 ,数据参数化的工具你肯定的懂一些 ,因为它能大大的提高我们自动化脚本编写效率 。

1.1什么是数据参数化

所谓的数据参数化 ,是指所执行的测试用例步骤相同、而数据不同 ,每次运行用例只变化的是数据 ,于是将这些数据专门放在一起进行批量循环运行 ,从而完成测试用例执行的目的 。

以登录功能为例 ,若一个登录功能每次操作的步骤是 :

  1. 输入用户名

  2. 输入密码

  3. 点击登录按钮 。

但是,因为每次输入的数据不同,导致生成的测试用例就不同了 ,同样还是这个登录功能,加上数据就变为以下的用例了 。

  • case1 : 输入正确的用户名 ,输入正确的密码 ,点击登录

  • case2 : 输入正确的用户,输入错误的密码,点击登录

  • case3 :输入正确的用户名,输入空的密码,点击登录

  • casen : ...

可以看到 ,在这些用例中,每条用例最大的不同是什么呢 ?其实就是数据不同 。但是由于数据不同,从而生成了多条测试用例 ,在功能测试中,这些用例是需要分别写、分别执行 。

1.2.为什么要进行数据参数化 ?

在功能测试中,即使是相同的步骤 ,只是数据不同 ,我们亦然也要尽量分开编写每一条用例 ,比如像上面的编写方式 ,因为这些编写它的易读性更好 ,功能测试设计测试用例和执行用例往往不是一个人 ,所以用例编写的易读性是就是一个很重要的因素 。

但是如果将上面的用例进行自动化实现 ,虽然按照一条用例对应一个方法是一种很清晰的思路 ,但是它的最大问题就是代码冗余 ,当一个功能中步骤相同,只是数据不同时,你的数据越多,代码冗余度就越高 。你会发现每个测试方法中的代码就会是相同的 。

像代码冗余这种问题,在编写自动化时是必须要考虑的一个问题,因为随着代码量越多 ,冗余度越高、越难维护 。

以下就是是通过正常方式实现登录的自动化脚本 :

import unittest
from package_unittest.login import loginclass TestLogin(unittest.TestCase):# case1 : 输入正确的用户名和正确的密码进行登录def test_login_success(self):expect_reslut = 0actual_result = login('admin','123456').get('code')self.assertEqual(expect_reslut,actual_result)# case2 : 输入正确的用户名和错误的密码进行登录def test_password_is_wrong(self):expect_reslut = 3actual_result = login('admin', '1234567').get('code')self.assertEqual(expect_reslut, actual_result)# case3 : 输入正确的用户名和空的密码进行登录def test_password_is_null(self):expect_reslut = 2actual_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 .

参数化工具支持测试框架备注
ddtunittest第三方包,需要下载安装
parameterizednose,unittest,pytest第三方包,需要下载安装
@pytest.mark.parametrizepytest本身属于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 parameterizedclass 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 parameterizedclass 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.001sOK

请注意,@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 parameterizeddef 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.001sOK

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.001sOK

最后@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 = 200def 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_classdef 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.项目总结

至此,我们已经实现了五步了 ,分别是 :

第一 、如何编写一个接口自动化框架 ,在第一篇博文中介绍了 。

第二、如何使用unittest编写测试用例 ,已经在第二篇博文中介绍了 。

第三、如何使用requests实现接口请求 ,并和测试用例如何对接 ,已经在第三篇博文中介绍了。

第四、如何使用yaml编写测试数据 ,已经在第四篇博文中介绍了 。

第五,如何使用allure生成测试报告,已经在第五篇博文中介绍了 。

第六 ,如何使用loguru记录日志 ,已经在第六篇博文中介绍了 。

第七,如何使用pymysql连接数据库,已经在第七篇博文中介绍了 。

第八,如何进行数据参数化 ,也就是本篇博文了 。

这篇关于自动化测试如何进行参数化?使用什么工具进行参数化?一文详解parameterized的使用以及如何在自动化中使用。的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/241659

相关文章

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

性能测试介绍

性能测试是一种测试方法,旨在评估系统、应用程序或组件在现实场景中的性能表现和可靠性。它通常用于衡量系统在不同负载条件下的响应时间、吞吐量、资源利用率、稳定性和可扩展性等关键指标。 为什么要进行性能测试 通过性能测试,可以确定系统是否能够满足预期的性能要求,找出性能瓶颈和潜在的问题,并进行优化和调整。 发现性能瓶颈:性能测试可以帮助发现系统的性能瓶颈,即系统在高负载或高并发情况下可能出现的问题

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

Hadoop数据压缩使用介绍

一、压缩原则 (1)运算密集型的Job,少用压缩 (2)IO密集型的Job,多用压缩 二、压缩算法比较 三、压缩位置选择 四、压缩参数配置 1)为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器 2)要在Hadoop中启用压缩,可以配置如下参数

Makefile简明使用教程

文章目录 规则makefile文件的基本语法:加在命令前的特殊符号:.PHONY伪目标: Makefilev1 直观写法v2 加上中间过程v3 伪目标v4 变量 make 选项-f-n-C Make 是一种流行的构建工具,常用于将源代码转换成可执行文件或者其他形式的输出文件(如库文件、文档等)。Make 可以自动化地执行编译、链接等一系列操作。 规则 makefile文件

字节面试 | 如何测试RocketMQ、RocketMQ?

字节面试:RocketMQ是怎么测试的呢? 答: 首先保证消息的消费正确、设计逆向用例,在验证消息内容为空等情况时的消费正确性; 推送大批量MQ,通过Admin控制台查看MQ消费的情况,是否出现消费假死、TPS是否正常等等问题。(上述都是临场发挥,但是RocketMQ真正的测试点,还真的需要探讨) 01 先了解RocketMQ 作为测试也是要简单了解RocketMQ。简单来说,就是一个分

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传

Andrej Karpathy最新采访:认知核心模型10亿参数就够了,AI会打破教育不公的僵局

夕小瑶科技说 原创  作者 | 海野 AI圈子的红人,AI大神Andrej Karpathy,曾是OpenAI联合创始人之一,特斯拉AI总监。上一次的动态是官宣创办一家名为 Eureka Labs 的人工智能+教育公司 ,宣布将长期致力于AI原生教育。 近日,Andrej Karpathy接受了No Priors(投资博客)的采访,与硅谷知名投资人 Sara Guo 和 Elad G

OpenHarmony鸿蒙开发( Beta5.0)无感配网详解

1、简介 无感配网是指在设备联网过程中无需输入热点相关账号信息,即可快速实现设备配网,是一种兼顾高效性、可靠性和安全性的配网方式。 2、配网原理 2.1 通信原理 手机和智能设备之间的信息传递,利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家应用和设备之间的发现。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规Sof