1 Unittest参数化
1.1 ddt
1.1.1 简介
- 数据驱动ddt可以实现测试数据与测试脚本的分离;
- 通过ddt来将测试数据加载到脚本中;
1.1.2 说明
- 测试数据为嵌套字典的列表;
- 测试类前加修饰
@ddt
; - 测试用例前加修饰
@data()
- 运行后用例会自动加载成多个单独的用例。
1.1.3 安装
pip install ddt
1.1.4 版本信息
C:\Users\Administrator>pip show ddt
Name: ddt
Version: 1.4.2
Summary: Data-Driven/Decorated Tests
Home-page: https://github.com/datadriventests/ddt
Author: Carles Barrobés
Author-email: carles@barrobes.com
License: UNKNOWN
Location: d:\python37\lib\site-packages
Requires:
Required-by:
1.1.5 实例1
# -*- coding:utf-8 -*-
# 作者:NoamaNelson
# 日期:2022/11/21
# 文件名称:test_unittest_ddt.py
# 作用:unittest数据驱动ddt
# 联系:VX(NoamaNelson)
# 博客:https://blog.csdn.net/NoamaNelson
import unittest
from ddt import *
test_case = [{"data": {"name": "NoamaNelson", "pwd": "123456"}, "info": {"msg": "登陆成功", "code": "200"}},
{"data": {"name": "noama", "pwd": "123456"}, "info": {"msg": "登陆失败,用户名或密码错误!", "code": "201"}},
{"data": {"name": "", "pwd": "123456"}, "info": {"msg": "用户名不能为空!", "code": "201"}},
{"data": {"name": "NoamaNelson", "pwd": ""}, "info": {"msg": "密码不能为空!", "code": "201"}},
{"data": {"name": "", "pwd": ""}, "info": {"msg": "用户名和密码不能为空!", "code": "201"}}]
@ddt
class TestCase(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
print("打开浏览器进入登陆界面")
@classmethod
def tearDownClass(cls) -> None:
print("关闭退出浏览器")
def login(self, name, pwd):
if name == "NoamaNelson" and pwd == "123456":
return {"msg": "登陆成功", "code": "200"}
elif name == "noama" and pwd == "123456":
return {"msg": "登陆失败,用户名或密码错误!", "code": "201"}
elif name == "" and pwd == "123456":
return {"msg": "用户名不能为空!", "code": "201"}
elif name == "NoamaNelson" and pwd == "":
return {"msg": "密码不能为空!", "code": "201"}
elif name == "" and pwd == "":
return {"msg": "用户名和密码不能为空!", "code": "201"}
else:
return False
@data(*test_case)
def test_case_data(self, case):
print(f"case:{case}")
print(f"case[info]:{case['info']}")
print(f"返回值为:{self.login(case['data']['name'], case['data']['pwd'])}")
self.assertEqual(case['info'], self.login(case['data']['name'], case['data']['pwd']))
if __name__ == '__main__':
unittest.main()
test_unittest_ddt.py::TestCase::test_case_data_1 打开浏览器进入登陆界面
PASSED
[ 20%]case:{'data': {'name': 'NoamaNelson', 'pwd': '123456'}, 'info': {'msg': '登陆成功', 'code': '200'}}
case[info]:{'msg': '登陆成功', 'code': '200'}
返回值为:{'msg': '登陆成功', 'code': '200'}
test_unittest_ddt.py::TestCase::test_case_data_2 PASSED
[ 40%]case:{'data': {'name': 'noama', 'pwd': '123456'}, 'info': {'msg': '登陆失败,用户名或密码错误!', 'code': '201'}}
case[info]:{'msg': '登陆失败,用户名或密码错误!', 'code': '201'}
返回值为:{'msg': '登陆失败,用户名或密码错误!', 'code': '201'}
test_unittest_ddt.py::TestCase::test_case_data_3 PASSED
[ 60%]case:{'data': {'name': '', 'pwd': '123456'}, 'info': {'msg': '用户名不能为空!', 'code': '201'}}
case[info]:{'msg': '用户名不能为空!', 'code': '201'}
返回值为:{'msg': '用户名不能为空!', 'code': '201'}
test_unittest_ddt.py::TestCase::test_case_data_4 PASSED
[ 80%]case:{'data': {'name': 'NoamaNelson', 'pwd': ''}, 'info': {'msg': '密码不能为空!', 'code': '201'}}
case[info]:{'msg': '密码不能为空!', 'code': '201'}
返回值为:{'msg': '密码不能为空!', 'code': '201'}
test_unittest_ddt.py::TestCase::test_case_data_5 PASSED
[100%]case:{'data': {'name': '', 'pwd': ''}, 'info': {'msg': '用户名和密码不能为空!', 'code': '201'}}
case[info]:{'msg': '用户名和密码不能为空!', 'code': '201'}
返回值为:{'msg': '用户名和密码不能为空!', 'code': '201'}
关闭退出浏览器
1.1.6 实例2
# -*- coding:utf-8 -*-
# 作者:NoamaNelson
# 日期:2022/11/21
# 文件名称:test_unittest_ddt1.py
# 作用:unittest数据驱动ddt
# 联系:VX(NoamaNelson)
# 博客:https://blog.csdn.net/NoamaNelson
import unittest
from ddt import *
num = [{"zhangsan": 10},
{"lisi": 20},
{"wangwu": 30},
{"zhaoliu": 40}]
lsit_num = [10, 20, 30, 40]
@ddt
class TestCase(unittest.TestCase):
@data(*num)
def test_case_data(self, case):
m = [i for i in case.values()]
print(f"case:{m[0]}")
self.assertIn(m[0], lsit_num)
if __name__ == '__main__':
unittest.main()
Ran 4 tests in 0.000s
OK
case:10
case:20
case:30
case:40
1.2 paramunittest
1.2.1 说明
paramunittest
参数化,传入的参数类型可以是元组,列表,字典,对象,函数;- 通过
@paramunittest.parametrized
装饰器传给调用者; - 通过
paramunittest.ParametrizedTestCase
执行测试案例; - 通过通过
setParameters
方法接收装饰器传递过来的参数。
1.2.2 安装
pip install paramunittest
1.2.3 版本信息
C:\Users\Administrator>pip show paramunittest
Name: ParamUnittest
Version: 0.2
Summary: Simple extension to have parametrized unit tests.
Home-page: https://github.com/rik0/ParamUnittest
Author: Enrico Franchi
Author-email: enrico.franchi@gmail.com
License: BSD
Location: d:\python37\lib\site-packages
Requires:
Required-by:
1.2.3 实例:参数传入元组数据
# -*- coding:utf-8 -*-
# 作者:NoamaNelson
# 日期:2022/11/21
# 文件名称:test_unittest_paramunittest.py
# 作用:unittest参数化paramunittest
# 联系:VX(NoamaNelson)
# 博客:https://blog.csdn.net/NoamaNelson
import unittest
import paramunittest
# 传入元组
@paramunittest.parametrized(
(4, 5),
(6, 7)
)
class TestCase(paramunittest.ParametrizedTestCase):
def setParameters(self, n1, n2):
self.n1 = n1
self.n2 = n2
def test_case(self):
print(self.n1, self.n2)
self.assertGreater(self.n2, self.n1)
if __name__ == '__main__':
unittest.main()
Ran 2 tests in 0.002s
OK
4 5
6 7
1.2.4 实例:参数传入列表数据
# -*- coding:utf-8 -*-
# 作者:NoamaNelson
# 日期:2022/11/21
# 文件名称:test_unittest_paramunittest.py
# 作用:unittest参数化paramunittest
# 联系:VX(NoamaNelson)
# 博客:https://blog.csdn.net/NoamaNelson
import unittest
import paramunittest
# 传入列表
@paramunittest.parametrized(
[4, 5],
[6, 7]
)
class TestCase(paramunittest.ParametrizedTestCase):
def setParameters(self, n1, n2):
self.n1 = n1
self.n2 = n2
def test_case(self):
print(self.n1, self.n2)
self.assertGreater(self.n2, self.n1)
if __name__ == '__main__':
unittest.main()
Ran 2 tests in 0.002s
OK
4 5
6 7
1.2.5 实例:参数传入字典数据
# -*- coding:utf-8 -*-
# 作者:NoamaNelson
# 日期:2022/11/21
# 文件名称:test_unittest_paramunittest.py
# 作用:unittest参数化paramunittest
# 联系:VX(NoamaNelson)
# 博客:https://blog.csdn.net/NoamaNelson
import unittest
import paramunittest
# 传入字典
@paramunittest.parametrized(
{"n1": 4, "n2": 5},
{"n1": 6, "n2": 7}
)
class TestCase(paramunittest.ParametrizedTestCase):
def setParameters(self, n1, n2):
self.n1 = n1
self.n2 = n2
def test_case(self):
print(self.n1, self.n2)
self.assertGreater(self.n2, self.n1)
if __name__ == '__main__':
unittest.main()
Ran 2 tests in 0.002s
OK
4 5
6 7
1.2.6 实例:参数传入对象
# -*- coding:utf-8 -*-
# 作者:NoamaNelson
# 日期:2022/11/21
# 文件名称:test_unittest_paramunittest.py
# 作用:unittest参数化paramunittest
# 联系:VX(NoamaNelson)
# 博客:https://blog.csdn.net/NoamaNelson
import unittest
import paramunittest
test_data = [{"n1": 4, "n2": 5}, {"n1": 6, "n2": 7}]
@paramunittest.parametrized(
*test_data
)
class TestCase(paramunittest.ParametrizedTestCase):
def setParameters(self, n1, n2):
self.n1 = n1
self.n2 = n2
def test_case(self):
print(self.n1, self.n2)
self.assertGreater(self.n2, self.n1)
if __name__ == '__main__':
unittest.main()
Ran 2 tests in 0.002s
OK
4 5
6 7
1.2.7 实例:参数传入函数
# -*- coding:utf-8 -*-
# 作者:NoamaNelson
# 日期:2022/11/21
# 文件名称:test_unittest_paramunittest.py
# 作用:unittest参数化paramunittest
# 联系:VX(NoamaNelson)
# 博客:https://blog.csdn.net/NoamaNelson
import unittest
import paramunittest
# 传入函数
def test_data():
return [{"n1": 4, "n2": 5}, {"n1": 6, "n2": 7}]
@paramunittest.parametrized(
*test_data()
)
class TestCase(paramunittest.ParametrizedTestCase):
def setParameters(self, n1, n2):
self.n1 = n1
self.n2 = n2
def test_case(self):
print(self.n1, self.n2)
self.assertGreater(self.n2, self.n1)
if __name__ == '__main__':
unittest.main()
Ran 2 tests in 0.002s
OK
4 5
6 7
1.2.8 实例:通过继承unittest.TestCase类执行案例
# -*- coding:utf-8 -*-
# 作者:NoamaNelson
# 日期:2022/11/21
# 文件名称:test_unittest_paramunittest.py
# 作用:unittest参数化paramunittest
# 联系:VX(NoamaNelson)
# 博客:https://blog.csdn.net/NoamaNelson
import unittest
import paramunittest
# 传入函数
def test_data():
return [{"n1": 4, "n2": 5}, {"n1": 6, "n2": 7}]
@paramunittest.parametrized(
*test_data()
)
# class TestCase(paramunittest.ParametrizedTestCase):
# def setParameters(self, n1, n2):
# self.n1 = n1
# self.n2 = n2
class TestCase(unittest.TestCase):
def setParameters(self, n1, n2):
self.n1 = n1
self.n2 = n2
def test_case(self):
print(self.n1, self.n2)
self.assertGreater(self.n2, self.n1)
if __name__ == '__main__':
unittest.main()
Ran 2 tests in 0.002s
OK
4 5
6 7
2 Pytest参数化
2.1 说明
pytest允许在多个级别启用测试参数化:
pytest.fixture()
允许fixture
有参数化功能(后面学习)@pytest.mark.parametrize
允许在测试函数或类中定义多组参数和fixtures
pytest_generate_tests
允许定义自定义参数化方案或扩展(拓展)
2.2 parametrize方法
2.2.1 参数说明
parametrize(argnames, argvalues, indirect=False, ids=None, scope=None)
参数 | 说明 | 格式 | 备注 |
---|---|---|---|
argnames |
参数名称 | 字符串"arg1,arg2,arg3" |
也可以是list 或者tuple |
argvalues |
参数值列表 | [ val1,val2,val3 ] | 多参数用元组存放[ (val1,val2), (val3, val4) ] |
indirect |
设置成True ,则把传进来的参数当函数执行,而不是一个参数 |
/ | / |
ids |
用例的ID |
字符串列表 | ids 的长度需要与测试数据列表的长度一致 |
scope |
用于控制Fixture的作用范围 | / | 默认"function" |
2.2.2使用参数化前后比对
2.2.2.1 使用前
def test_case_o():
assert 10 + 10 == 20
def test_case_t():
assert 30 - 10 == 20
def test_case_th():
assert 4 * 5 == 20
def test_case_f():
assert 40 / 2 == 20
- 从以上代码看,四个用例的共同规则是两个数加减乘除后进行判断;
- 这样写需要写四个用例,感觉比较累赘;
- 我们可以尝试使用参数化处理。
2.2.2.2 使用后
# -*- coding:utf-8 -*-
# 作者:NoamaNelson
# 日期:2022/11/21
# 文件名称:test_pytest_parametrize.py
# 作用:pytest参数化
# 联系:VX(NoamaNelson)
# 博客:https://blog.csdn.net/NoamaNelson
import pytest
@pytest.mark.parametrize("num, result", [("10 + 10", 20),
("30 - 10", 20),
("4 * 5", 20),
("40 / 2", 20)])
def test_case(num, result):
print(f"num:{num}")
print(f"result:{result}")
assert eval(num) == result
if __name__ == '__main__':
pytest.main(["-s", "test_pytest_parametrize.py"])
test_pytest_parametrize.py
num:10 + 10
result:20
.
num:30 - 10
result:20
.
num:4 * 5
result:20
.
num:40 / 2
result:20
.
2.3 常用场景
2.3.1 装饰测试类
- 当装饰器
@pytest.mark.parametrize
装饰测试类时,会将数据集合传递给类的所有测试用例方法。
# -*- coding:utf-8 -*-
# 作者:NoamaNelson
# 日期:2022/11/21
# 文件名称:test_pytest_parametrize1.py
# 作用:pytest参数化
# 联系:VX(NoamaNelson)
# 博客:https://blog.csdn.net/NoamaNelson
import pytest
@pytest.mark.parametrize("a, b, result", [(10, 10, 0)])
class TestP:
def test_case_1(self, a, b, result):
assert a - b == result
def test_case_2(self, a, b, result):
assert a - b == result
if __name__ == '__main__':
pytest.main(["-s", "test_pytest_parametrize1.py"])
test_pytest_parametrize1.py ..
============================== 2 passed in 0.34s ==============================
2.3.2 “笛卡尔积”,多个参数化装饰器
- 一个函数或一个类可以装饰多个
@pytest.mark.parametrize
; - 最终生成的用例数是
n*m
,比如上面的代码就是:参数a的数据有3个,参数b的数据有3个,所以最终的用例数有3*3=9
条。
# -*- coding:utf-8 -*-
# 作者:NoamaNelson
# 日期:2022/11/21
# 文件名称:test_pytest_parametrize2.py
# 作用:pytest参数化
# 联系:VX(NoamaNelson)
# 博客:https://blog.csdn.net/NoamaNelson
import pytest
@pytest.mark.parametrize("a", [10, 20, 30])
@pytest.mark.parametrize("b", [40, 50, 60])
def test_case_1(a, b):
print(f"测试数据为{a}, {b}")
if __name__ == '__main__':
pytest.main(["-s", "test_pytest_parametrize2.py"])
test_pytest_parametrize2.py 测试数据为10, 40
.测试数据为20, 40
.测试数据为30, 40
.测试数据为10, 50
.测试数据为20, 50
.测试数据为30, 50
.测试数据为10, 60
.测试数据为20, 60
.测试数据为30, 60
.
============================== 9 passed in 0.06s ==============================
2.3.3 参数化传入字典数据
# -*- coding:utf-8 -*-
# 作者:NoamaNelson
# 日期:2022/11/21
# 文件名称:test_pytest_parametrize3.py
# 作用:pytest参数化
# 联系:VX(NoamaNelson)
# 博客:https://blog.csdn.net/NoamaNelson
import pytest
data = ({"xiaozhang": 23}, {"xiaoli": 25}, {"laowang": 66})
@pytest.mark.parametrize("nian_ling", data)
def test_case_1(nian_ling):
print(nian_ling)
if __name__ == '__main__':
pytest.main(["-s", "test_pytest_parametrize3.py"])
test_pytest_parametrize3.py
{'xiaozhang': 23}
.
{'xiaoli': 25}
.
{'laowang': 66}
.
2.3.4 参数化标记数据
# -*- coding:utf-8 -*-
# 作者:NoamaNelson
# 日期:2022/11/21
# 文件名称:test_pytest_parametrize4.py
# 作用:pytest参数化
# 联系:VX(NoamaNelson)
# 博客:https://blog.csdn.net/NoamaNelson
import pytest
data = [("100+100", 200), ("400-200", 200),
pytest.param("100*2", 200, marks=pytest.mark.skip),
pytest.param("400/2", 100, marks=pytest.mark.xfail)]
@pytest.mark.parametrize("num, result", data)
def test_case_1(num, result):
print(f"num:{num}--->result:{result}")
assert eval(num) == result
if __name__ == '__main__':
pytest.main(["-s", "test_pytest_parametrize4.py"])
test_pytest_parametrize4.py
num:100+100--->result:200
.
num:400-200--->result:200
.
s
num:400/2--->result:100
x
=================== 2 passed, 1 skipped, 1 xfailed in 0.16s ===================
2.3.5 参数化增加可读性
# -*- coding:utf-8 -*-
# 作者:NoamaNelson
# 日期:2022/11/21
# 文件名称:test_pytest_parametrize5.py
# 作用:pytest参数化
# 联系:VX(NoamaNelson)
# 博客:https://blog.csdn.net/NoamaNelson
import pytest
data = [(10, 20, 200), (40, 50, 2000)]
ids = [f"a:{a} * b:{b} = result:{result}" for a, b, result in data]
@pytest.mark.parametrize("a, b, result", data, ids=ids)
class TestCase:
def test_case_1(self, a, b, result):
print(f"用例1输入数据为:{a}, {b},结果为:{result}")
assert a * b == result
def test_case_2(self, a, b, result):
print(f"用例2输入数据为:{a}, {b},结果为:{result}")
assert a * b == result
if __name__ == '__main__':
pytest.main(["-s", "test_pytest_parametrize5.py"])
test_pytest_parametrize5.py::TestCase::test_case_1[a:10 * b:20 = result:200] PASSED [ 25%]用例1输入数据为:10, 20,结果为:200
test_pytest_parametrize5.py::TestCase::test_case_1[a:40 * b:50 = result:2000] PASSED [ 50%]用例1输入数据为:40, 50,结果为:2000
test_pytest_parametrize5.py::TestCase::test_case_2[a:10 * b:20 = result:200] PASSED [ 75%]用例2输入数据为:10, 20,结果为:200
test_pytest_parametrize5.py::TestCase::test_case_2[a:40 * b:50 = result:2000] PASSED [100%]用例2输入数据为:40, 50,结果为:2000
============================== 4 passed in 0.04s ==============================
.