微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

pytest——04 pytest.mark.xxx

使用 @pytest.mark.xxx 标记测试用例


可以标记测试方法、测试类,标记名可以自定义,最好起有意义的名字;
同一测试类/方法可同时拥有多个标记

# test_login_logout.py
 
import pytest
 
 
@pytest.mark.login
class TestLogin:
    """登陆功能测试类"""
 
    @pytest.mark.smoke
    @pytest.mark.success
    def test_login_sucess(self):
        """登陆成功"""
 
        # 实现登陆逻辑
        pass
 
    @pytest.mark.Failed
    def test_login_Failed(self):
        """登陆失败"""
 
        # 实现登陆逻辑
        pass
 
 
@pytest.mark.logout
class Testlogout:
    """登出功能测试类"""
 
    @pytest.mark.smoke
    @pytest.mark.success
    def test_logout_sucess(self):
        """登出成功"""
 
        # 实现登出功能
        pass
 
    @pytest.mark.Failed
    def test_logout_Failed(self):
        """登出失败"""
 
        # 实现登出功能
        pass
运行标记的用例:
使用 -m 参数运行标记的测试用例;
-m 参数支持 and、or 、not 等表达式;
# 运行登陆功能的用例
pytest.main(['-m login'])
# 运行登出功能的用例
pytest.main(['-m logout'])
# 运行功能成功的用例
pytest.main(['-m success'])
# 运行功能失败的用例
pytest.main(['-m Failed'])
# 运行登陆功能但是不运行登陆失败的测试用例
pytest.main(['-m login and not Failed'])
# 运行登出功能但是不运行登出成功的测试用例
pytest.main(['-m logout and not success'])
# 运行登陆和登出的用例
pytest.main(['-m login or logout'])

注册、管理 mark 标记


当使用 -m 参数执行 mark 标记的用例时,pytest 会发出告警信息 “PytestUnkNownMarkWarning: UnkNown pytest.mark.login - is this a typo? ”,告诉你这是一个 pytest 未知的一个标记!为了消除告警,我们需要在 pytest 的配置文件注册 mark 标记

注册 mark 标记

注册方式:

1、单个标签

在conftest.py添加如下代码

def pytest_configure(config):
  # demo是标签名
  config.addinivalue_line("markers", "demo:示例运行") 

2、多个标签

在conftest.py添加如下代码

def pytest_configure(config):
  marker_list = ["testdemo", "demo", "smoke"] # 标签名集合
  for markers in marker_list:
    config.addinivalue_line("markers", markers)

3、添加pytest.ini 配置文件(在你项目的任意一个文件下,新建一个file,文件命名为pytest.ini)

[pytest]
markers=
  smoke:this is a smoke tag
  demo:demo
  testdemo:testdemo

首先在项目根目录创建一个文件 pytest.ini ,这个是 pytest 的配置文件
然后在 pytest.ini 文件的 markers 中写入你的 mark 标记, 冒号 “:” 前面是标记名称,后面是 mark 标记的说明,可以是空字符串;
注意:pytest.ini 文件中只能使用纯英文字符,绝对不能使用中文的字符(尤其是冒号和空格)

规范使用 mark 标记


注册完 mark 标记之后 pytest 便不会再告警,但是有时手残容易写错 mark 名,导致 pytest 找不到用例,一时想不开很难debug,尤其是团队协作时很容易出现类似问题,所以我们需要 “addopts = --strict” 参数来严格规范 mark 标记的使用!

在 pytest.ini 文件添加参数 “addopts = --strict”;
注意要另起一行,不要在 markers 中添加
添加该参数后,当使用未注册的 mark 标记时,pytest会直接报错:“ 'xxx' not found in `markers` configuration option ”,不执行测试任务;
注意:pytest.ini 配置文件不支持注释,不支持注释,不支持注释...

使用Marks标记测试用例

通过使用pytest.mark你可以轻松地在测试用例上设置元数据。例如, 一些常用的内置标记

  • @pytest.mark.skip - 始终跳过该测试用例
  • @pytest.mark.skipif - 遇到特定情况跳过该测试用例
  • @pytest.mark.xfail - 遇到特定情况,产生一个“期望失败”输出
  • @pytest.mark.parametrize - 在同一个测试用例上运行多次调用(译者注: 参数化数据驱动)
  • 标记分类用例:@pytest.mark.levell
  • 标记用例执行顺序:@pytest.mark.run(order=1)(需安装pytest-rodering)
  • 标记使用指定Fixtures(测试准备及清理方法):@pyest.mark.usefixtures()
  • 标记超时时间:@pytest.make.timeout(60)(需安装pytest-timeout)
  • 标记失败重跑次数:@pytest.mark.flaky(reruns=3,reruns_delay=1)reruns=3:测试用例失败重跑3次,reruns_delay=1:用例失败则延迟1秒后重跑(需安装pytest-rerunfailurse)

注意:
标记只对测试用例有效,对fixtures方法无效。

 @pytest.mark.skip

跳过测试用例的最简单方法是使用skip装饰器标记,可以传递一个可选的原因reason参数(str,跳过测试函数的原因):

@pytest.mark.skip(reason="目前没办法测试该用例")
def test_the_unkNown():
    ...

 

或者,也可以在测试执行或setup期间,通过调用pytest.skip(reason)函数强制跳过该用例

def test_function():
    if not valid_config():
        pytest.skip("不支持该配置")

当在导入时间内无法评估跳过条件时,这种在用例跳过的方法非常有用。

也可以在模块级别跳过整个模块:pytest.skip(reason,allow_module_level=True)

import sys
import pytest

if not sys.platform.startswith("win"):
    pytest.skip("跳过只支持Windows平台的用意",allow_module_level=True)

(使用此方法跳过整个模块时会抛出Skipped异常,如果在脚本下方使用pytest.main()执行,会执行不到。此时应使用命令行pytest命令执行。)

@pytest.mark.skipif  指定条件下跳过

参数:

  • condition(bool
  • reason(str) - 跳过测试函数的原因。
import sys
import pytest

@pytest.mark.skipif(sys.version_info<(3,6),reason="需要python3.6版本以上")
def test_function():
    ...

如果收集用例时,skipif中的条件表达式计算为True,则将跳过测试函数,运行时使用-rs,会在运行结果摘要显示指定的原因。

你可以skipif在模块之间共享标记,参考以下测试脚本:

# test_mymodule.py模块内容
import mymodule

minversion = pytest.mark.skipif(
    mymodule.__versioninfo__ < (1,1),reason="至少mymodule-1.1版本以上"
)

@minversion
def test_function():
    ...

导入mark标记并在一个测试模块中重复使用它:

# test_myothermodule.py
from test_mymodule import minversion

@minversion
def test_anotherfunction():
    ...

对于较大的测试套件,通常最好有一个文件来定义标记,然后在整个测试套件中一致地应用这些标记

跳过类或模块的所有测试用例

@pytest.mark.skipif(sys.platform=="win32",reason="不在Windows系统运行")
class TestPosixCalls(object):
    def test_function(self):
        "在Win32平台不执行setup和该测试方法"

如果用例使用了多个skipif装饰器时,任意一个跳过条件为真,则将跳过该用例。

跳过文件或目录

有时你可能需要跳过整个文件或目录,例如,如果测试依赖于Python版本特定的函数或包含你不希望运行Pytest的代码在这种情况下,你必须从测试集合中排除文件和目录

自定义测试集

你可以轻松地指示pytest从每个Python文件中发现测试:

# content of pytest.ini
[pytest]
python_files = *.py

但是,许多项目将具有setup.py不希望导入的项目。此外,可能只有特定的python版本可以导入文件在这种情况下,您可以通过在conftest.py文件中列出来动态定义要忽略的文件

# content of conftest.py
import sys

collect_ignore = ["setup.py"]
if sys.version_info[0] > 2:
    collect_ignore.append("pkg/module_py2.py")

然后,如果您有这样的模块文件: 

# content of pkg/module_py2.py
def test_only_on_python2():
    try:
        assert 0
    except Exception, e:
        pass

一个setup.py虚拟文件,像这样:

# content of setup.py
0 / 0  # will raise exception if imported

如果您使用Python 2解释器运行,则将找到一个测试并忽略setup.py文件

如果您使用Python 3解释器运行,则一次测试和setup.py 文件都将被忽略

通过向中添加模式,也可以忽略基于Unix Shell样式通配符的文件collect_ignore_glob。

# content of conftest.py
import sys

collect_ignore = ["setup.py"]
if sys.version_info[0] > 2:
    collect_ignore_glob = ["*_py2.py"]

跳过缺少的导入依赖关系

跳过缺少的导入依赖关系

你可以在模块级别测试或测试setup方法中使用以下帮助程序

docutils = pytest.importorskip("docutils")

如果docutils无法在此处导入,则会导致测试跳过结果

参数分析:

  1. modname: 需要被导入的模块名称,比如 selenium;
  2. minversion: 表示需要导入的最小的版本号,如果该版本不达标,将会打印出报错信息;
  3. reason: 只有当模块没有被导入时,给定该参数将会显示出给定的消息内容

@pytest.mark.xfail

标记用例,标记期望这个用例执行失败
带有次标记的用例,会正常执行,只是失败,不会显示堆栈信息。
用例执行结果:

  1. 用例执行失败,XFAIL,符合预期的结果
  2. 用例执行成功,XPASS,不符合预期结果
@pytest.mark.xfail
def test_1():
  ...

pytest.mark.xfail参数说明

condition: 如果满足条件,那么就标记用例执行失败
reason: 表明失败原因
raises: 认None,可指定一个异常类或者异常类元组,表明我们期望用例抛出这些异常。如果用例失败不是因为这些异常,那么用例会执行失败,并标记Failed
run: 认值为True,若为False,则用例不执行,直接标记为XFAIL
strict:认值False,
strict=False,如用例执行失败,标记为XFAIL,用例执行成功标记为XPASS
strict=True,如果用例执行成功,标记Failed,而不再是XPASS

2. pytest.xfail

在用例中标记用例执行失败,可在setup/case/teardown中使用

def test_1():
  if ...:
    pytest.xfail('Failed')
 ...

当在固定的条件下,像已知的bug或者特性下,使用@pytest.mark.xfail是比较好的

3. 命令行选项

pytest --runxfail,使标记过的xfail的用例正常运行,就像没有标记过一样,其中pytest.xfail()也会失效

@pytest.mark.parametrize

1. @pytest.mark.parametrize("参数名",列表数据)


参数名:用来接收每一项数据,并作为测试用例的参数。
列表数据:一组测试数据。

1.1  一个参数一个

# 一个参数一个值
@pytest.mark.parametrize("input", ["输入值"])
def test_case1(input):
    print("\n" + input)
    assert input == "输入值"

1.2 一个参数多个值

#====参数为列表嵌套字典====

import pytest

def secend(c,b):
    add= int(b)+int(c)
    print(add)
    return add

@pytest.mark.parametrize('case',[{'b':'34','c':'56'},{'b':'39','c':'56'}])
def test_1(case):
    pp=case
    tt=secend(**case)
    print("测试账号:%s" % tt)


if __name__ == "__main__":
    pytest.main(["-s", "1.py"])

 #====参数为列表嵌套元组====


import pytest
test_datas = [
    (11, 22, 33),
    (22, 33, 55)
]

@pytest.mark.parametrize("data", test_datas)
def test_add02(data):
    res = data[0] + data[1]
    print(res)
    assert res == data[2]

if __name__=="__main__":
    pytest.main(['-s','1.py'])

 #====参数为列表嵌套列表====

import pytest
test_datas = [
    [11, 22, 33],
    [22, 33, 55]
]

@pytest.mark.parametrize("data", test_datas)
def test_add02(data):
    res = data[0] + data[1]
    print(res)
    assert res == data[2]

if __name__=="__main__":
    pytest.main(['-s','1.py'])

1.3 多个参数多个值

@pytest.mark.parametrize("userName,passWord",
                         [("xiaoqiang", "123456"), ("rose", "123456"), ("jone", "123456"), ("Alex", "123456")])
def test_login(userName, passWord):
    print(userName + " : " + passWord)
    assert userName == 'rose'

 1.4 多个参数的混合使用

有点类似笛卡尔积的意思,示例代码如下:

data1 = [1, 2]
data2 = ["python", "java"]
data3 = ["软", "件", "测", "试", "君"]


@pytest.mark.parametrize("a", data1)
@pytest.mark.parametrize("b", data2)
@pytest.mark.parametrize("c", data3)
def test_case3(a, b, c):
    print(f"生成新的数据组合为:[{a} {b} {c}]")

运行结果如下:

2.参数化集合标记的使用

@pytest.mark.parametrize("user,pwd",
                         [("xiaoqiang", "123456"), ("rose", "123456"),
                          pytest.param("jone", "123456", marks=pytest.mark.xfail),
                          pytest.param("Alex", "123456", marks=pytest.mark.skip)])
def test_login(user, pwd):
    print(user + " : " + pwd)
    assert user == "rose"

3. pytest.mark.parametrize 结合fixture使用

登录函数为例,下面展示只用pytest.mark.parametrize实现的方法

import pytest
#定义一组测试数据
test_login_data = [("admin", "123456"),
            ("admin", " ")]

def login(user, psw):
'''登录函数'''
  print("账户:%s" %user)
  print("密码%s" %psw)
  if psw:
    return True
  else:
    return False

@pytest.mark.parametrize("user, psw", test_login_data)    #这是login的测试函数,在用例上用上装饰器,从而直接使用参数
def test_login(user, psw):
'''用例'''
  result = login(user, psw)
  assert result == True, "失败原因:密码为空"


if __name__ == "__main__":
  pytest.main(["-v", "test_demo.py"])
使用装饰器@pytest.fixture写登录函数登录放到前置条件里,使用@pytest.fixture装饰器:使用认的request参数
如:user = request.param 用来接收传入的参数
import pytest
# 定义一组测试数据
test_user_data = ["admin1", "admin2"]

@pytest.fixture(scope="module")
def login(request):
  user = request.param
  print("账户:%s"%user)
  return user

@pytest.mark.parametrize("login", test_user_data, indirect=True)     #indire=True标识login是个函数,只是在这被用作了参数
def test_login(login):
'''这是一条用例'''
  a = login
  print("用例中login的返回值:%s" % a)
  assert a != ""


if __name__ == "__main__":
  pytest.main(["-v", "test_demo.py"])

或者

# content of test_05.py

# coding:utf-8
import pytest


canshu = [{"user":"amdin", "psw":"111"}]

@pytest.fixture(scope="module")
def login(request):
    user = request.param["user"]
    psw = request.param["psw"]
    print("正在操作登录,账号:%s, 密码%s" % (user, psw))
    if psw:
        return True
    else:
        return False


@pytest.mark.parametrize("login", canshu, indirect=True)
class Test_xx():

    def test_01(self, login):
        '''用例1登录'''
        result = login
        print("用例1:%s" % result)
        assert result == True


    def test_02(self, login):
        result = login
        print("用例3,登录结果:%s" % result)
        if not result:
            pytest.xfail("登录不成功, 标记为xfail")

        assert 1 == 1

    def test_03(self, login):
        result = login
        print("用例3,登录结果:%s" %result)
        if not result:
            pytest.xfail("登录不成功, 标记为xfail")

        assert 1 == 1


if __name__ == "__main__":
    pytest.main(["-s", "test_05.py"])

当有多个登录用户

# coding:utf-8
import pytest


canshu = [{"user":"amdin", "psw":"111"},{"user":"amdin2", "psw":"222"}]

@pytest.fixture(scope="module")
def login(request):
    user = request.param["user"]
    psw = request.param["psw"]
    print("正在操作登录,账号:%s, 密码%s" % (user, psw))
    if psw:
        return True
    else:
        return False


@pytest.mark.parametrize("login", canshu, indirect=True)
class Test_xx():

    def test_01(self, login):
        '''用例1登录'''
        result = login
        print("用例1:%s" % result)
        assert result == True


    def test_02(self, login):
        result = login
        print("用例3,登录结果:%s" % result)
        if not result:
            pytest.xfail("登录不成功, 标记为xfail")

        assert 1 == 1

    def test_03(self, login):
        result = login
        print("用例3,登录结果:%s" %result)
        if not result:
            pytest.xfail("登录不成功, 标记为xfail")

        assert 1 == 1


if __name__ == "__main__":
    pytest.main(["-s", "test_parametrize_and_fixture.py"])

当scope="function"时,执行顺序是每个test先把两个canshu执行完成后,才执行下一个case

test_parametrize_and_fixture.py::test_01[login0] 正在操作登录,账号:amdin, 密码:111
PASSED                  [ 10%]用例1:True

test_parametrize_and_fixture.py::test_01[login1] 正在操作登录,账号:amdin2, 密码:222
PASSED                  [ 20%]用例1:True

test_parametrize_and_fixture.py::test_02[login0] 正在操作登录,账号:amdin, 密码:111
PASSED                  [ 30%]用例2,登录结果:True

test_parametrize_and_fixture.py::test_02[login1] 正在操作登录,账号:amdin2, 密码:222
PASSED                  [ 40%]用例2,登录结果:True

test_parametrize_and_fixture.py::Test_xx::test_03[login0] 正在操作登录,账号:amdin, 密码:111
PASSED         [ 50%]用例3:True

test_parametrize_and_fixture.py::Test_xx::test_03[login1] 正在操作登录,账号:amdin2, 密码:222
PASSED         [ 60%]用例3:True

test_parametrize_and_fixture.py::Test_xx::test_04[login0] 正在操作登录,账号:amdin, 密码:111
PASSED         [ 70%]用例4,登录结果:True

test_parametrize_and_fixture.py::Test_xx::test_04[login1] 正在操作登录,账号:amdin2, 密码:222
PASSED         [ 80%]用例4,登录结果:True

test_parametrize_and_fixture.py::Test_xx::test_05[login0] 正在操作登录,账号:amdin, 密码:111
PASSED         [ 90%]用例5,登录结果:True

test_parametrize_and_fixture.py::Test_xx::test_05[login1] 正在操作登录,账号:amdin2, 密码:222
PASSED         [100%]用例5,登录结果:True


============================= 10 passed in 0.07s ==============================

Process finished with exit code 0

当scope="class"时,执行顺序如下

test_parametrize_and_fixture.py::test_01[login0] 正在操作登录,账号:amdin, 密码:111
PASSED                  [ 10%]用例1:True

test_parametrize_and_fixture.py::test_02[login0] 正在操作登录,账号:amdin, 密码:111
PASSED                  [ 20%]用例2,登录结果:True

test_parametrize_and_fixture.py::test_01[login1] 正在操作登录,账号:amdin2, 密码:222
PASSED                  [ 30%]用例1:True

test_parametrize_and_fixture.py::test_02[login1] 正在操作登录,账号:amdin2, 密码:222
PASSED                  [ 40%]用例2,登录结果:True

test_parametrize_and_fixture.py::Test_xx::test_03[login0] 正在操作登录,账号:amdin, 密码:111
PASSED         [ 50%]用例3:True

test_parametrize_and_fixture.py::Test_xx::test_04[login0] PASSED         [ 60%]用例4,登录结果:True

test_parametrize_and_fixture.py::Test_xx::test_05[login0] PASSED         [ 70%]用例5,登录结果:True

test_parametrize_and_fixture.py::Test_xx::test_03[login1] 正在操作登录,账号:amdin2, 密码:222
PASSED         [ 80%]用例3:True

test_parametrize_and_fixture.py::Test_xx::test_04[login1] PASSED         [ 90%]用例4,登录结果:True

test_parametrize_and_fixture.py::Test_xx::test_05[login1] PASSED         [100%]用例5,登录结果:True


============================= 10 passed in 0.07s ==============================

Process finished with exit code 0

note所以涉及到前后关联的case一定要注意执行顺序

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。

相关推荐