pytest学习和使用12-Unittest和Pytest参数化详解

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了pytest学习和使用12-Unittest和Pytest参数化详解相关的知识,希望对你有一定的参考价值。

(12-Unittest和Pytest参数化详解)

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
# 博客: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
# 博客: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
# 博客: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
# 博客: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
# 博客: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
# 博客: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
# 博客: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
# 博客: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参数化
# 博客: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参数化
# 博客: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参数化
# 博客: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参数化
# 博客: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参数化
# 博客: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参数化
# 博客: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 ==============================
.

以上是关于pytest学习和使用12-Unittest和Pytest参数化详解的主要内容,如果未能解决你的问题,请参考以下文章

pytest学习和使用9-fixture中conftest.py如何使用?

pytest学习和使用 ✨2 初步使用和用例运行

python学习-pytest-fixture

pytest学习和使用18-pytest.ini配置文件如何使用?

python-pytest学习(十七)-conftest.py作用范围

python-pytest学习(十七)-conftest.py作用范围