关于pytest

pytest单元测试框架:

pytest的安装

  1. 下载

pip install pytest

 

  1. 测试是否安装成功

C:\Users\Anthony>pytest --version
This is pytest version 5.3.5, imported from c:\python36\lib\site-packages\pytest\__init__.py
setuptools registered plugins:
  allure-pytest-2.8.9 at c:\python36\lib\site-packages\allure_pytest\plugin.py
  pytest-forked-1.1.3 at c:\python36\lib\site-packages\pytest_forked\__init__.py
  pytest-html-2.0.1 at c:\python36\lib\site-packages\pytest_html\plugin.py
  pytest-metadata-1.8.0 at c:\python36\lib\site-packages\pytest_metadata\plugin.py
  pytest-ordering-0.6 at c:\python36\lib\site-packages\pytest_ordering\__init__.py
  pytest-rerunfailures-8.0 at c:\python36\lib\site-packages\pytest_rerunfailures.py
  pytest-xdist-1.31.0 at c:\python36\lib\site-packages\xdist\plugin.py
  pytest-xdist-1.31.0 at c:\python36\lib\site-packages\xdist\looponfail.py

 

目录结构:

M:\py_tests\ # 我的是M盘的 py_tests 目录,所有操作都在 py_tests 目录内完成
    ├─scripts   
    │  ├─test_case_dir1
    │  │  ├─test_case_02.py    # 用例脚本文件
    │  │  └─__init__.py
    │  ├─test_allure_case.py   # 脚本文件
    │  ├─test_case_01.py   # 脚本文件
    │  └─__init__.py
    ├─report
    │  ├─report.html   # pytest-html生成的用例报告
    │  ├─assets  # allure的依赖目录
    │  ├─result  # allure生成的用例数据
    │  └─allure_html   # allure生成的用例报告目录
    |     └─index.html  # allure生成的最终的html类型的测试报告  
    ├─case_set.py
    ├─demo0.py   # 用例脚本文件
    ├─demo1.py   # 用例脚本文件
    ├─pytest.ini  # 配置文件
    └─__init__.py
 

踩坑:你创建的pytest脚本名称中不允许含有.,比如1.简单上手.py,这样会报错。当然,可以这么写1-简单上手.py

简单上手

注意,pytest运行的脚本名称,不能含有. ,比如 2.内容回顾.py,你可以写成 2-内容回顾.py,否则报错:

ModuleNotFoundError: No module named '2'

 

解决collected 0 items的问题。 在pytest执行脚本的过程中,它会读取以test开头的函数,或者以Test开头的类。

2-pytest简单使用.py:


import pytest
​
def test_case01():
    print('执行用例01.......')
    assert 0  # 断言失败
def test_case02():
    print('执行用例02.......')
    assert 1  # 断言成功
    
def custom_case03():
    print('执行用例03.......')
    assert 1  # 断言成功
​
​
if __name__ == '__main__':
    pytest.main(["-s",'2-pytest简单使用.py'])

View Code

 

当执行完毕,用例通过使用.表示,F表示断言失败。

上例中,当我们在执行(就像Python解释器执行普通的Python脚本一样)测试用例的时候,pytest.main(["-s", "demo1.py"])中的传参需要是一个元组或者列表(我的pytest是5.2.2版本),之前的版本可能需要这么调用pytest.main("-s demo1.py"),传的参数是str的形式,至于你使用哪种,取决于报不报错:

TypeError: `args` parameter expected to be a list or tuple of strings, got: '-s demo1.py' (type: <class 'str'>)

 

遇到上述报错,就是参数需要一个列表或者元组的形式,而我们使用的是str形式。

上述代码正确的执行结果是这样的:


===================================================== test session starts ======================================================
platform win32 -- Python 3.6.2, pytest-5.2.2, py-1.8.0, pluggy-0.13.0
rootdir: M:\py_tests
collected 2 items                                                                                                               
​
demo1.py 执行用例01.......
F执行用例02.......
.
​
=========================================================== FAILURES ===========================================================
_________________________________________________________ test_case01 __________________________________________________________def test_case01():
        print('执行用例01.......')
>       assert 0  # 断言失败
E       assert 0
​
demo1.py:11: AssertionError
================================================= 1 failed, 1 passed in 0.13s ==================================================

View Code

 

大致的信息就是告诉我们:

  • collected 2 items:本次执行中,收集了2个用例。

  • 完了开始执行用例,.表示执行成功,F表示执行失败。

  • 脚本中的第一个用例执行失败;第二个用例执行成功;但是第三个也就是custom_case03并没有执行,由此我们知道,pytest只识别以test_开头的用例。

pytest.main([“-s”, “demo1.py”])参数说明

  • -s,在控制台中输出print的结果,有些情况下,ptest不会展示print的输出,所以使用-s参数来实现。

  • demo1.py是要执行的脚本名称。

pytest中的setup和teardown

在pytest中,setupteardown有以下几种:

  • 模块级别的setup和teardown,作用于当年模块内所有的用例执行性前后

    • setup_module

    • teardown_module


import pytest
​
​
def setup_module():
    """ 模块级别的 setup,在该脚本内所有用例集执行之前触发执行 """
    print('模块级别的 setup.....')
​
def test_case01():
    print('执行用例01.......')
    assert 0  # 断言失败
def test_case02():
    print('执行用例02.......')
    assert 1  # 断言成功
def teardown_module():
    """ 模块级别的 teardown,在该脚本内所有用例集执行之后触发执行 """
    print('模块级别的 teardown.....')
​
if __name__ == '__main__':
    pytest.main(["-s", "demo1.py"])
M:\py_tests>python demo1.py
========================================================== test session starts ===========================================================
platform win32 -- Python 3.6.2, pytest-5.2.2, py-1.8.0, pluggy-0.13.0
rootdir: M:\py_tests
collected 2 items                                                                                                                         
​
demo1.py 模块级别的 setup.....
执行用例01.......
F执行用例02.......
.模块级别的 teardown.....
​
​
================================================================ FAILURES ================================================================
______________________________________________________________ test_case01 _______________________________________________________________def test_case01():
        print('执行用例01.......')
>       assert 0  # 断言失败
E       assert 0
​
demo1.py:16: AssertionError
====================================================== 1 failed, 1 passed in 0.12s =======================================================

View Code

 

 

  • 函数级别的setup和teardown,作用于每个函数的执行前后

    • setup_function

    • teardown_function


import pytest

def setup_function():
    """ 函数级别的 setup,在该脚本内每个用例函数执行之前触发执行 """
    print('函数级别的 setup.....')

def test_case01():
    print('执行用例01.......')
    assert 0  # 断言失败

def test_case02():
    print('执行用例02.......')
    assert 1  # 断言成功

def teardown_function():
    """ 函数级别的 teardown,在该脚本内每个用例函数执行之后触发执行 """
    print('函数级别的 teardown.....')


if __name__ == '__main__':
    pytest.main(["-s", "demo1.py"])
M:\py_tests>python demo1.py
========================================================== test session starts ===========================================================
platform win32 -- Python 3.6.2, pytest-5.2.2, py-1.8.0, pluggy-0.13.0
rootdir: M:\py_tests
collected 2 items                                                                                                                         

demo1.py 函数级别的 setup.....
执行用例01.......
F函数级别的 teardown.....
函数级别的 setup.....
执行用例02.......
.函数级别的 teardown.....


================================================================ FAILURES ================================================================
______________________________________________________________ test_case01 _______________________________________________________________

    def test_case01():
        print('执行用例01.......')
>       assert 0  # 断言失败
E       assert 0

demo1.py:16: AssertionError
====================================================== 1 failed, 1 passed in 0.11s =======================================================
 

View Code

 

  • 类级别的setup和teardown,作用于类中所有用例的前后

    • setup_class

    • teardown_class


import pytest

class TestCase(object):

    def setup_class(self):
        """ 类级别的 setup,在该类中内用例集执行之前触发执行 """
        print('类级别的 setup.....')

    def teardown_class(self):
        """ 类级别的 teardown,在该类中内用例集执行之后触发执行 """
        print('类级别的 teardown.....')

    def test_case01(self):
        """ 用例 01 """
        print('执行用例01.......')
        assert 0  # 断言失败

    def test_case02(slef):
        """ 用例 02 """
        print('执行用例02.......')
        assert 1  # 断言成功

if __name__ == '__main__':
    pytest.main(["-s", "demo1.py"])
M:\py_tests>python demo1.py
========================================================== test session starts ===========================================================
platform win32 -- Python 3.6.2, pytest-5.2.2, py-1.8.0, pluggy-0.13.0
rootdir: M:\py_tests
collected 2 items                                                                                                                         
​
demo1.py 类级别的 setup.....
执行用例01.......
F执行用例02.......
.类级别的 teardown.....
​
​
================================================================ FAILURES ================================================================
__________________________________________________________ TestCase.test_case01 __________________________________________________________
​
self = <demo1.TestCase object at 0x0363F710>def test_case01(self):
        """ 用例 01 """
        print('执行用例01.......')
>       assert 0  # 断言失败
E       assert 0
​
demo1.py:53: AssertionError
====================================================== 1 failed, 1 passed in 0.10s =======================================================

View Code

 

  • 类中用例级别的setup和teardown

    • setup_method

    • teardown_method


import pytest
​
class TestCase(object):
​
    def setup_method(self):
        """ 类中方法级别的 setup,在该类中内每个用例执行之前触发执行 """
        print('类中方法级别的 setup.....')
​
    def teardown_method(self):
        """ 类中方法级别的 teardown,在该类中内每个用例执行之后触发执行 """
        print('类中方法级别的 teardown.....')
​
    def test_case01(self):
        """ 用例 01 """
        print('执行用例01.......')
        assert 0  # 断言失败
def test_case02(slef):
        """ 用例 02 """
        print('执行用例02.......')
        assert 1  # 断言成功
if __name__ == '__main__':
    pytest.main(["-s", "demo1.py"])
​
M:\py_tests>python demo1.py
========================================================== test session starts ===========================================================
platform win32 -- Python 3.6.2, pytest-5.2.2, py-1.8.0, pluggy-0.13.0
rootdir: M:\py_tests
collected 2 items                                                                                                                         

demo1.py 类中方法级别的 setup.....
执行用例01.......
F类中方法级别的 teardown.....
类中方法级别的 setup.....
执行用例02.......
.类中方法级别的 teardown.....


================================================================ FAILURES ================================================================
__________________________________________________________ TestCase.test_case01 __________________________________________________________

self = <demo1.TestCase object at 0x042BA2D0>

    def test_case01(self):
        """ 用例 01 """
        print('执行用例01.......')
>       assert 0  # 断言失败
E       assert 0

demo1.py:49: AssertionError
====================================================== 1 failed, 1 passed in 0.42s =======================================================

View Code

 

小结

  • 在类中,不需要__init__方法。

  • 测试类的类名必须以Test开头。

  • 类中的测试方法编写规则跟函数一致。

配置文件

该脚本有多种运行方式,如果处于PyCharm环境,可以使用右键或者点击运行按钮运行,也就是在pytest中的主函数中运行:

if __name__ == '__main__':
    pytest.main(["-s", "demo1.py"])   # 就是调用的 pytest 的 main 函数

 

也可以在命令行中运行:

M:\py_tests>python demo1.py

 

这种方式,跟使用Python解释器执行Python脚本没有什么两样。也可以如下面这么执行:

M:\py_tests>pytest -s demo1.py

 

当然,还有一种是使用配置文件运行,来看看怎么用。

在项目的根目录下,我们可以建立一个pytest.ini文件,在这个文件中,我们可以实现相关的配置:

[pytest]
addopts = -s -v
testpaths = ./scripts
python_files = test_*.py
python_classes = Test*
python_functions = test_*

 

注意,配置文件中不许有中文

各参数:

  • addopts, 执行时读取的参数多个参数以空格分割,其他参数后续用到再说。

    • -v,控制pytest输出结果的详细程度

    • -s,在pytest的输出中,输出我们自己写的print,默认是不输出的。

  • testpaths,收集所有的用例集合。python_files,读取以该参数指定前缀的脚本。

    • 因为我们用例可能分布在不同的目录或文件中,那么这个scripts就是我们所有文件或者目录的顶层目录。其内的子文件或者子目录都要以test_开头,pytest才能识别到。

    • 另外,上面这么写,是从一个总目录下寻找所有的符合条件的文件或者脚本,那么我们想要在这个总目录下执行其中某个具体的脚本文件怎么办?

[pytest]
testpaths = ./scripts/
python_files = test_case_01.py

 

这么写就是执行scripts目录下面的test_case_01.py这个文件。

  • python_classes,定义类的类名必须是Test

  • python_functions,定义函数的函数名以test_开头才能识别。

配置配置文件的注意事项:

  • scripts目录一般跟pytest.ini文件同级别。

  • pytest.ini文件中不允许有中文;并且该文件必须在项目的根目录下面。

跳过用例

我们知道在unittest中,跳过用例可以用skip,那么这同样是适用于pytest。


import pytest

@pytest.mark.skip(reason='无条件跳过')
def test_case_01():
    assert 1


@pytest.mark.skipif(condition=1 < 2, reason="condition条件为真时跳过用例")
def test_case_02():

    assert 1

View Code

 

跳过用例,我们使用@pytest.mark.skipif(condition, reason)

  • condition表示跳过用例的条件。

  • reason表示跳过用例的原因。

然后将它装饰在需要被跳过用例的的函数上面。

效果如下:


M:\py_tests>pytest                                                                                                                   

scripts/test_allure_case.py::test_case_01 SKIPPED
scripts/test_allure_case.py::test_case_02 SKIPPED

=========================================================== 2 skipped in 0.14s ===========================================================

View Code

 

上例执行结果相对详细,因为我们在配置文件中为addopts增加了-v,之前的示例结果中,没有加! 另外,此时,在输出的控制台中, 还无法打印出reason信息,如果需要打印,则可以在配置文件中的addopts参数的-s变为-rs

[pytest]
addopts = -rs -v
testpaths = ./scripts
python_files = test_*.py
python_classes = Test*
python_functions = test_*

 

标记预期失败

如果我们事先知道测试函数会执行失败,但又不想直接跳过,而是希望显示的提示。

Pytest 使用 pytest.mark.xfail实现预见错误功能::

xfail(condiition, reason, [raises=None, run=True, strict=False])

 

需要掌握的必传参数的是:

  • condition,预期失败的条件,当条件为真的时候,预期失败。

  • reason,失败的原因。

场景: 某个用例我预期它会失败,那就对这个接口标记一下:

  • 预期失败,实际执行结果是失败。

  • 预期失败,但是实际执行结果是成功的。如果有特殊要求的话,那么该用例我们可以将它认为是执行失败。

  • 来看示例:


import pytest


class TestCase(object):

    @pytest.mark.xfail(1 < 2, reason='预期失败, 执行失败')
    def test_case_01(self):
        """ 预期失败, 执行也是失败的 """
        print('预期失败, 执行失败')
        assert 0

    @pytest.mark.xfail(1 < 2, reason='预期失败, 执行成功')
    def test_case_02(self):
        """ 预期失败, 但实际执行结果却成功了 """
        print('预期失败, 执行成功')
        assert 1

View Code

 

结果如下:


M:\py_tests>pytest
========================================================== test session starts ===========================================================
platform win32 -- Python 3.6.2, pytest-5.2.2, py-1.8.0, pluggy-0.13.0
rootdir: M:\py_tests, inifile: pytest.ini, testpaths: ./scripts/
plugins: allure-pytest-2.8.6, cov-2.8.1, forked-1.1.3, html-2.0.0, metadata-1.8.0, ordering-0.6, rerunfailures-7.0, xdist-1.30.0
collected 2 items                                                                                                                         
​
scripts\demo1.py xX                                                                                                                 [100%]
​
===================================================== 1 xfailed, 1 xpassed in 0.15s =================================================

View Code

 

pytest 使用 x 表示预见的失败(XFAIL)。

如果预见的是失败,但实际运行测试却成功通过,pytest 使用 X 进行标记(XPASS)。

而在预期失败的两种情况中,我们不希望出现预期失败,结果却执行成功了的情况出现,因为跟我们想的不一样嘛,我预期这条用例失败,那这条用例就应该执行失败才对,你虽然执行成功了,但跟我想的不一样,你照样是失败的!

所以,我们需要将预期失败,结果却执行成功了的用例标记为执行失败,可以在pytest.ini文件中,加入:

[pytest]
xfail_strict=true

 

这样就就把上述的情况标记为执行失败了。

scripts/test_xfail.py::test_case_01 FAILED
scripts/test_xfail.py::test_case_02 XFAIL
scripts/test_xfail.py::test_case_03 PASSED
scripts/test_xfail.py::test_case_04 FAILED
scripts/test_xfail.py::test_case_01 XPASS

 

如果在配置文件中,添加了xfail_strict=true,表示如果预期失败,确执行成功的的用例标记为失败。

  • FAILED:用例执行失败

  • XFAIL:预期失败,实际执行结果也是失败

  • PASSED:正常的用例通过标识

  • FAILED:正常的用例失败标识

  • XPASS:预期失败,执行结果却成功的标识。

 

参数化:parametrize

在 pytest 中,我们有更好的解决方法,就是参数化测试,即每组参数都独立执行一次测试。使用的工具就是 pytest.mark.parametrize(argnames, argvalues)

pytest支持参数化,及从外部引入数据进行测试。

@pytest.mark.parametrize(argnames, argvalues):

  • argnames:表示每次从argvalues中提取的一个元素

  • argvalues:表示要传的数据,如列表、元组、字典、集合


import pytest
​
mobile_list = ['10010', '10086']
​
@pytest.mark.parametrize('mobile', mobile_list)
def test_register(mobile):
    """ 通过手机号注册 """
    print('注册手机号是: {}'.format(mobile))

View Code

 

来看(重要部分)结果::


M:\py_tests>pytest
scripts/test_case_01.py::test_register[10010] 注册手机号是: 10010
PASSED
scripts/test_case_01.py::test_register[10086] 注册手机号是: 10086
PASSED
​
====================================================== 2 passed in 0.11s ======================================================

View Code

 

可以看到,列表内的每个手机号,都是一条测试用例。

 

多参数:


import pytest
​
mobile_list = ['10010', '10086']
code_list = ['x2zx', 'we2a']
​
@pytest.mark.parametrize('mobile,code', zip(mobile_list, code_list))
def test_register(mobile, code):
    """ 通过手机号注册 """
    print('注册手机号是: {} 验证码是: {}'.format(mobile, code))

View Code

 

在多参数情况下,多个参数名是以,分割的字符串。参数值是列表嵌套的形式组成的。


M:\py_tests>pytest                   

scripts/test_case_01.py::test_register[10010-x2zx] 注册手机号是: 10010 验证码是: x2zx
PASSED
scripts/test_case_01.py::test_register[10086-we2a] 注册手机号是: 10086 验证码是: we2a
PASSED

====================================================== 2 passed in 0.44s ======================================================

View Code

 

固件:@pytest.fixture()

固件,简单来说,就是测试夹具,类似于setup/teardown的功能,但是相对的比较灵活。

我们可以利用固件做任何事情,其中最常见的可能就是数据库的初始连接和最后关闭操作。

Pytest 使用 pytest.fixture() 定义固件,下面是最简单的固件,访问主页前必须先登录:


import pytest

@pytest.fixture()
def login():   # 固件
    print("登录成功")

def test_index(login):  # 该用例执行之前要登录
    print("主页")
    assert 1

View Code

 

在用例函数中传递固件的函数名。

结果:


M:\py_tests>pytest               

scripts/test_case_01.py::test_index 登录....
主页....
PASSED

====================================================== 1 passed in 0.13s =======================================================

View Code

 

作用域#

在之前的示例中,你可能会觉得,这跟之前的setup和teardown的功能也类似呀,但是,fixture相对于setup和teardown来说更灵活。pytest通过scope参数来控制固件的使用范围,也就是作用域。

在定义固件时,通过 scope 参数声明作用域,可选项有:

  • function: 函数级,每个测试函数都会执行一次固件;

  • class: 类级别,每个测试类执行一次,所有方法都可以使用;

  • module: 模块级,每个模块执行一次,模块内函数和方法都可使用;

  • session: 会话级,一次测试只执行一次,所有被找到的函数和方法都可用。

默认的作用域为 function

比如之前的login固件,可以指定它的作用域:


import pytest

@pytest.fixture(scope='function')
def login():
    print('登录....')

def test_index(login):
    print('主页....')

View Code

 

 

预处理和后处理

很多时候需要在测试前进行预处理(如新建数据库连接),并在测试完成进行清理(关闭数据库连接)。

当有大量重复的这类操作,最佳实践是使用固件来自动化所有预处理和后处理。

Pytest 使用 yield 关键词将固件分为两部分,yield 之前的代码属于预处理,会在测试前执行;yield 之后的代码属于后处理,将在测试完成后执行。

以下测试模拟数据库查询,使用固件来模拟数据库的连接关闭:


import pytest

@pytest.fixture()
def db():
    print('Connection successful')

    yield

    print('Connection closed')

def search_user(user_id):
    d = {
        '001': 'xiaoming',
        '002': 'xiaohua'
    }
    return d[user_id]

def test_case_01(db):
    assert search_user('001') == 'xiaoming'

def test_case_02(db):
    assert search_user('002') == 'xiaohua'

View Code

 

结果:


M:\py_tests>pytest                    

scripts/test_case_01.py::test_case_01 Connection successful
PASSEDConnection closed

scripts/test_case_01.py::test_case_02 Connection successful
PASSEDConnection closed

====================================================== 2 passed in 0.15s =======================================================

View Code

 

可以看到在两个测试用例执行前后都有预处理和后处理。

 

失败重跑:rerun

当用例执行失败后,可以指定重新运行的次数,常用来处理网络不佳的情况下。

使用之前下载:

pip install pytest-rerunfailures

 

使用 在配置文件的addopts参数后跟 –reruns=3

addopts = -s -v --reruns=3

 

使用的时候,正常的写用例和执行即可,当检测到用例执行失败,就会自动的触发rerun,直到在规定次数内尝试执行用例成功,那该用例就执行成功了,否则重新尝试至规定的次数,然后该用例执行失败。

来看示例:


import pytest

def test_case01():
    print('执行用例01.......')
    assert 1  # 断言成功

def test_case02():
    print('执行用例02.......')
    assert 0  # 断言失败,需要重新执行

class TestCaseClass(object):

    def test_case_03(self):
        print('执行用例03.......')
        assert 1

View Code

 

结果:


M:\py_tests>pytest
======================================================= test session starts ========================================================
platform win32 -- Python 3.6.2, pytest-5.2.2, py-1.8.0, pluggy-0.13.0
rootdir: M:\py_tests, inifile: pytest.ini, testpaths: ./scripts/
plugins: allure-pytest-2.8.6, html-2.0.0, metadata-1.8.0, ordering-0.6, rerunfailures-7.0
collected 3 items                                                                                                                   

scripts\test_case_01.py 执行用例01.......
.执行用例02.......
R执行用例02.......
R执行用例02.......
R执行用例02.......
F执行用例03.......
.

============================================================= FAILURES =============================================================
___________________________________________________________ test_case02 ____________________________________________________________

    def test_case02():
        print('执行用例02.......')
>       assert 0  # 断言失败,需要重新执行
E       assert 0

scripts\test_case_01.py:19: AssertionError
------------------------------------ generated html file: file://M:\py_tests\report\report.html ------------------------------------
=============================================== 1 failed, 2 passed, 3 rerun in 0.20s ===============================================

View Code

 

我们也可以从用例报告中看出重试的结果:

img

上面演示了用例失败了,然后重新执行多少次都没有成功,这是一种情况。

接下来,来看另一种情况,那就是用例执行失败,重新执行次数内通过了,那么剩余的重新执行的次数将不再执行。


import random
import pytest

def test_case01():
    print('执行用例01.......')
    assert 1  # 断言成功

def test_case02():
    print('执行用例02.......')
    status = random.randint(0, 2)
    if status:
        assert 1  # 断言成功,无需再重复执行了
    else:
        assert 0  # 断言失败,需要重新执行
class TestCaseClass(object):

    def test_case_03(self):
        print('执行用例03.......')
        assert 1

View Code

 

通过random模块帮助我们演示出在某次执行中出现失败的情况,而在重新执行的时候,会出现成功的情况,看结果:


M:\py_tests>pytest
======================================================= test session starts ========================================================
platform win32 -- Python 3.6.2, pytest-5.2.2, py-1.8.0, pluggy-0.13.0
rootdir: M:\py_tests, inifile: pytest.ini, testpaths: ./scripts/
plugins: allure-pytest-2.8.6, html-2.0.0, metadata-1.8.0, ordering-0.6, rerunfailures-7.0
collected 3 items                                                                                                                   

scripts\test_case_01.py 执行用例01.......
.执行用例02.......
R执行用例02.......
.执行用例03.......
.

------------------------------------ generated html file: file://M:\py_tests\report\report.html ------------------------------------
==================================================== 3 passed, 1 rerun in 0.08s ====================================================

View Code

 

可以看到,用例02重新执行了一次就成功了,剩余的两次执行就终止了。

控制用例执行顺序:pytest-ordering

默认的, pytest中,用例的执行顺序是从上到下的形式。如果有需要自定义控制用例执行顺序的话,就需要用ordering插件。

下载

pip install pytest-ordering

 

使用:


import pytest

@pytest.mark.run(order=2)
def test_case_02():
    assert 1
@pytest.mark.run(order=1)
def test_case_01():
    assert 1
@pytest.mark.run(order=3)
def test_case_03():
    assert 1

class TestCase(object):

    def test_case_06(self):
        assert 1

    def test_case_05(self):
        assert 1


    @pytest.mark.run(order=4)
    def test_case_04(self):
        assert 1

View Code

 

如果,有排序的和五排序的用例同时存在,则优先执行有排序的;在安装找默认的从上到下的顺序执行无排序的。

html插件:pytest-html

在pytest中,要想生成测试报告,需要单独下载测试报告插件:

pip install pytest-html

 

https://github.com/pytest-dev/pytest-html

如果下载失败,可以使用PyCharm下载,怎么用PyCharm下载这里无需多言了吧。

使用,编辑配置文件:

[pytest]
addopts = -s --html=./report/report.html

 

完事之后,让我们继续终端中使用pytest重新跑测试用例,用例结果就不展示了,跟上面的结果一样,我们关注项目目录下的report/report.html文件,我们用浏览器打开它,你会发现:

img

allure

 

allure插件安装

Allure框架是一个灵活的轻量级多语言测试报告工具,它不仅以web的方式展示了简洁的测试结果,而且允许参与开发过程的每个人从日常执行的测试中最大限度的提取有用信息。 从开发人员(dev,developer)和质量保证人员(QA,Quality Assurance)的角度来看,Allure报告简化了常见缺陷的统计:失败的测试可以分为bug和被中断的测试,还可以配置日志、步骤、fixture、附件、计时、执行历史以及与TMS和BUG管理系统集成,所以,通过以上配置,所有负责的开发人员和测试人员可以尽可能的掌握测试信息。 从管理者的角度来看,Allure提供了一个清晰的“大图”,其中包括已覆盖的特性、缺陷聚集的位置、执行时间轴的外观以及许多其他方便的事情。allure的模块化和可扩展性保证了我们总是能够对某些东西进行微调。

少扯点,来看看怎么使用。

allure是第三方的轻量级的测试报告插件:

  • 易安装

  • 界面优美

  • 可配置性强

allure依赖Java环境,你要安装Java 环境:https://www.cnblogs.com/Neeo/articles/11954283.html

allure插件下载:百度云盘链接:链接:https://pan.baidu.com/s/1Xj1A_xsRscOZHskTR4xjAg 提取码:6b33

或者去https://github.com/allure-framework/allure2/releases下载

 

 

allure插件安装: 解压压缩包到一个没有中文的目录中,然后将其中的bin目录添加到环境变量(PATH)中

 

 

 

 

测试,终端输入:

 

 

Python安装allure模块

Python通过allure模块生成allure需要的中间数据——json数据。

在使用allure插件读取json数据,进而生成allure测试报告。

 

下载allure模块:

pip install allure-pytest

 

使用

一般使用allure要经历几个步骤:

  • 配置pytest.ini文件。

  • 编写用例并执行。

  • 使用allure工具生成html报告。

配置pytest.ini文件:

[pytest]
addopts = --alluredir ./report/result

 

在终端中输入pytest正常执行测试用例即可:


import pytest

def test_case_01():
    assert 1

def test_case_02():
    assert 0

def test_case_03():
    assert 1

View Code

 

执行完毕后,在项目的根目下,会自动生成一个report目录,这个目录下有:

  • report.html是我们的之前的pytest-html插件生成的HTML报告,跟allure无关。

  • result和assets目录是allure插件生成的测试报告文件,但此时该目录内还没有什么HTML报告,只有一些相关数据。

接下来需要使用allure工具来生成HTML报告。

此时我们在终端(如果是windows平台,就是cmd),路径是项目的根目录,执行下面的命令。

PS:我在pycharm中的terminal输入allure提示’allure’ 不是内部或外部命令,也不是可运行的程序或批处理文件。但windows的终端没有问题。

M:\py_tests>allure generate report/result -o report/allure_html --clean
Report successfully generated to report\allure_html

 

命令的意思是,根据report\result目录中的数据(这些数据是运行pytest后产生的)。在report目录下新建一个allure_html目录,而这个目录内有index.html才是最终的allure版本的HTML报告;如果你是重复执行的话,使用--clean清除之前的报告。

结果很漂亮:

img

allure open 默认的,allure报告需要HTTP服务器来打开,一般我们可以通过pycharm来完成,另外一种情况就是通过allure自带的open命令来完成。

  • 使用pycharm打开

 

 

  • 第二种就是使用allure自带的open命令打开

 

 

 

其他用法

当然,故事还是没有完!在使用allure生成报告的时候,在编写用例阶段,还可以有一些参数可以使用:

  • title,自定义用例标题,标题默认是用例名。

  • description,测试用例的详细说明。

  • feature和story被称为行为驱动标记,因为使用这个两个标记,通过报告可以更加清楚的掌握每个测试用例的功能和每个测试用例的测试场景。或者你可以理解为feature是模块,而story是该模块下的子模块。

  • allure中对bug的严重(severity)级别也有定义,allure使用

    severity

    来标识测试用例或者测试类的bug级别,分为blocker,critical,normal,minor,trivial5个级别。一般,bug分为如下几个级别:

    • Blocker级别:中断缺陷(客户端程序无响应,无法执行下一步操作),系统无法执行、崩溃或严重资源不足、应用模块无法启动或异常退出、无法测试、造成系统不稳定。

    • Critical级别:即影响系统功能或操作,主要功能存在严重缺陷,但不会影响到系统稳定性。比如说一个服务直接不可用了,微信不能发消息,支付宝不能付款这种,打开直接报错。

    • Major:即界面、性能缺陷、兼容性。如操作界面错误(包括数据窗口内列名定义、含义是否一致)、长时间操作无进度提示等。

    • Normal级别:普通缺陷(数值计算错误),是指非核心业务流程产生的问题,比如说知乎无法变更头像,昵称等。这个要看自己的定义。

    • Minor/Trivial级别:轻微缺陷(必输项无提示,或者提示不规范),比如各种影响体验,但不影响使用的内容。

  • dynamic,动态设置相关参数。

为用例添加标题和描述信息allure.title与allure.description


import pytest
import allure

@allure.title('测试用例标题1')
@allure.description('这是测试用例用例1的描述信息')
def test_case_01():
    assert 1

def test_case_02():
    assert 0

def test_case_03():
    assert 1

View Code

 

img

feature和story

行为驱动标记,简单理解为为用例分类。


import pytest
​
import allure
​
@allure.feature("登录用例")
class TestLogin(object):
​
    @allure.story("登录用例1")
    @allure.title("登录用例1")
    def test_case_01(self):
        assert 1
​
    @allure.story("登录用例2")
    @allure.title("登录用例2")
    def test_case_02(self):
        assert 1
​
    @allure.story("登录用例3")
    @allure.title("登录用例3")
    def test_case_03(self):
        assert 1
​
@allure.feature("注册用例")
class TestRegister(object):
    @allure.story("注册用例1")
    @allure.title("注册用例1")
    def test_case_01(self):
        assert 1
​
    @allure.story("注册用例2")
    @allure.title("注册用例2")
    def test_case_02(self):
        assert 1
​
    @allure.story("注册用例3")
    @allure.title("注册用例3")
    def test_case_03(self):
        assert 1
 

View Code

 

 

 

 

severity

allure中对bug的严重(severity)级别也有定义,allure使用severity来标识测试用例或者测试类的bug级别,分为blocker,critical,normal,minor,trivial5个级别。一般,bug分为如下几个级别:

  • Blocker级别:中断缺陷(客户端程序无响应,无法执行下一步操作),系统无法执行、崩溃或严重资源不足、应用模块无法启动或异常退出、无法测试、造成系统不稳定。

  • Critical级别:即影响系统功能或操作,主要功能存在严重缺陷,但不会影响到系统稳定性。比如说一个服务直接不可用了,微信不能发消息,支付宝不能付款这种,打开直接报错。

  • Major:即界面、性能缺陷、兼容性。如操作界面错误(包括数据窗口内列名定义、含义是否一致)、长时间操作无进度提示等。

  • Normal级别:普通缺陷(数值计算错误),是指非核心业务流程产生的问题,比如说知乎无法变更头像,昵称等。这个要看自己的定义。

  • Minor/Trivial级别:轻微缺陷(必输项无提示,或者提示不规范),比如各种影响体验,但不影响使用的内容。

 


import pytest
import allure
​
@allure.feature('登录模块')
class TestCaseLogin(object):
​
    @allure.severity(allure.severity_level.BLOCKER)
    def test_case_01(self):
        assert 1
​
    @allure.severity(allure.severity_level.CRITICAL)
    def test_case_02(self):
        assert 1
​
    @allure.severity(allure.severity_level.MINOR)
    def test_case_03(self):
        assert 1
​
    @allure.severity(allure.severity_level.TRIVIAL)
    def test_case_04(self):
        assert 1def test_case_05(self):
        assert 1

View Code

 


severity的默认级别是normal,所以上面的用例5可以不添加装饰器了。

img

dynamic

在参数化的时候,我们无法使用allure的相关功能,因为装饰器中是写死的功能,在参数中,数据都是在用例中使用。所以,我们采用allure.dynamic参数动态的为用例添加相关的功能。


import requests
import pytest
import allure
​
​
​
url_list = [
    {'url': "https://www.baidu.com", "method": "get", "status": 200, "title": "百度"},
    {'url': "https://www.cnblogs.com/Neeo/articles/11832655.html", "method": "get", "status": 200, "title":"cnblogs"},
    {'url': "http://www.neeo.cc:6001/post", "method": "post", "status": 200, "title":"post接口"},
    {'url': "http://www.neeo.cc:6001/put", "method": "put", "status": 200, "title":"put接口"},
]
​
​
@pytest.mark.parametrize('item', url_list)
def test_case(item):
​
    allure.dynamic.title(item['title'])
    response = requests.request(method=item['method'], url=item['url'])
    # print(response.status_code,  item['status'])
    assert response.status_code == item['status']

View Code

 

 

并发执行#

一条一条用例的执行,肯定会很慢,来看如何并发的执行测试用例,当然这需要相应的插件。

下载

pip install pytest-xdist

 

使用

在配置文件中添加:

[pytest]
addopts =  -v -s --html=report/report.html -n=auto
;addopts = -s --alluredir ./report/result
testpaths = ./scripts/
python_files = test_case_01.py
python_classes = Test*
python_functions = test_*

 

就是这个-n=auto

  • -n=auto,自动侦测系统里的CPU数目。

  • -n=numprocesses,也就是自己指定运行测试用例的进程数。

并发的配置可以写在配置文件中,然后其他正常的执行用例脚本即可。另外一种就是在终端中指定,先来看示例:


import pytest
​
def test_case01():
    print('执行用例01.......')
    assert 1  # 断言成功
​
​
@pytest.mark.skipif(condition= 2 > 1, reason='跳过用例')
def test_case02():
    print('执行用例02.......')
    assert 0  # 断言失败
class TestCaseClass(object):
​
    def test_case_03(self):
        print('执行用例03.......')
        assert 1def test_case_04(self):
        print('执行用例04.......')
        assert 1

View Code

 

结果:


M:\py_tests>pytest .\scripts\test_case_01.py -s -n auto
======================================================= test session starts ========================================================
platform win32 -- Python 3.6.2, pytest-5.2.2, py-1.8.0, pluggy-0.13.0 -- c:\python36\python.exe
cachedir: .pytest_cache
metadata: {'Python': '3.6.2', 'Platform': 'Windows-10-10.0.14393-SP0', 'Packages': {'pytest': '5.2.2', 'py': '1.8.0', 'pluggy': '0.13
.0'}, 'Plugins': {'allure-pytest': '2.8.6', 'forked': '1.1.3', 'html': '2.0.0', 'metadata': '1.8.0', 'ordering': '0.6', 'rerunfailure
s': '7.0', 'xdist': '1.30.0'}, 'JAVA_HOME': 'C:\\Program Files\\Java\\jdk1.8.0_201'}
rootdir: M:\py_tests, inifile: pytest.ini
plugins: allure-pytest-2.8.6, forked-1.1.3, html-2.0.0, metadata-1.8.0, ordering-0.6, rerunfailures-7.0, xdist-1.30.0
[gw0] win32 Python 3.6.2 cwd: M:\py_tests
[gw1] win32 Python 3.6.2 cwd: M:\py_tests
[gw2] win32 Python 3.6.2 cwd: M:\py_tests
[gw3] win32 Python 3.6.2 cwd: M:\py_tests
[gw0] Python 3.6.2 (v3.6.2:5fd33b5, Jul  8 2017, 04:14:34) [MSC v.1900 32 bit (Intel)]
[gw1] Python 3.6.2 (v3.6.2:5fd33b5, Jul  8 2017, 04:14:34) [MSC v.1900 32 bit (Intel)]
[gw2] Python 3.6.2 (v3.6.2:5fd33b5, Jul  8 2017, 04:14:34) [MSC v.1900 32 bit (Intel)]
[gw3] Python 3.6.2 (v3.6.2:5fd33b5, Jul  8 2017, 04:14:34) [MSC v.1900 32 bit (Intel)]
gw0 [4] / gw1 [4] / gw2 [4] / gw3 [4]
scheduling tests via LoadScheduling
​
scripts/test_case_01.py::test_case02
scripts/test_case_01.py::TestCaseClass::test_case_04
scripts/test_case_01.py::TestCaseClass::test_case_03
scripts/test_case_01.py::test_case01
[gw3] PASSED scripts/test_case_01.py::TestCaseClass::test_case_04
[gw0] PASSED scripts/test_case_01.py::test_case01
[gw2] PASSED scripts/test_case_01.py::TestCaseClass::test_case_03
[gw1] SKIPPED scripts/test_case_01.py::test_case02
​
------------------------------------ generated html file: file://M:\py_tests\report\report.html ------------------------------------
=================================================== 3 passed, 1 skipped in 2.23s ===================================================

View Code

 

pytest-sugar#

pytest-sugar 改变了 pytest 的默认外观,添加了一个进度条,并立即显示失败的测试。它不需要配置,只需 下载插件即可,用 pytest 运行测试,来享受更漂亮、更有用的输出。

下载

pip install pytest-sugar

 

其他照旧执行用例即可。

pytest-cov#

pytest-covpytest 中增加了覆盖率支持,来显示哪些代码行已经测试过,哪些还没有。它还将包括项目的测试覆盖率。

下载

pip install pytest-cov

 

使用

在配置文件中:

[pytest]
addopts =  -v -s --html=report/report.html -n=auto --cov=./scripts
;addopts = -s --alluredir ./report/result
testpaths = ./scripts/
python_files = test_case_01.py
python_classes = Test*
python_functions = test_*

 

也就是配置--cov=./scripts,这样,它就会统计所有scripts目录下所有符合规则的脚本的测试覆盖率。

执行的话,就照常执行就行。

结果:


M:\py_tests>pytest
Test session starts (platform: win32, Python 3.6.2, pytest 5.2.2, p
ytest-sugar 0.9.2)
cachedir: .pytest_cache
metadata: {'Python': '3.6.2', 'Platform': 'Windows-10-10.0.14393-SP
0', 'Packages': {'pytest': '5.2.2', 'py': '1.8.0', 'pluggy': '0.13.
0'}, 'Plugins': {'allure-pytest': '2.8.6', 'cov': '2.8.1', 'forked'
: '1.1.3', 'html': '2.0.0', 'metadata': '1.8.0', 'ordering': '0.6',
 'rerunfailures': '7.0', 'sugar': '0.9.2', 'xdist': '1.30.0'}, 'JAV
A_HOME': 'C:\\Program Files\\Java\\jdk1.8.0_201'}
rootdir: M:\py_tests, inifile: pytest.ini, testpaths: ./scripts/
plugins: allure-pytest-2.8.6, cov-2.8.1, forked-1.1.3, html-2.0.0,
metadata-1.8.0, ordering-0.6, rerunfailures-7.0, sugar-0.9.2, xdist
-1.30.0
[gw0] win32 Python 3.6.2 cwd: M:\py_tests
[gw1] win32 Python 3.6.2 cwd: M:\py_tests
[gw2] win32 Python 3.6.2 cwd: M:\py_tests
[gw3] win32 Python 3.6.2 cwd: M:\py_tests
[gw0] Python 3.6.2 (v3.6.2:5fd33b5, Jul  8 2017, 04:14:34) [MSC v.1900 32 bit (Intel)]
[gw1] Python 3.6.2 (v3.6.2:5fd33b5, Jul  8 2017, 04:14:34) [MSC v.1900 32 bit (Intel)]
[gw2] Python 3.6.2 (v3.6.2:5fd33b5, Jul  8 2017, 04:14:34) [MSC v.1900 32 bit (Intel)]
[gw3] Python 3.6.2 (v3.6.2:5fd33b5, Jul  8 2017, 04:14:34) [MSC v.1900 32 bit (Intel)]
gw0 [4] / gw1 [4] / gw2 [4] / gw3 [4]
scheduling tests via LoadScheduling
​
​
 scripts\test_case_01.py::test_case02 s                                                                                          50% █████
     pts\test_case_01.py::test_case01 ✓                                                                                          25% ██▌
    ] PASSED scripts/test_case_01.py
​
​
 scripts\test_case_01.py::TestCaseClass.test_case_03 ✓                                                                           75% ████
███▌  
​
 scripts\test_case_01.py::TestCaseClass.test_case_04 ✓                                                                          100% ████
██████ scripts/test_case_01.py
            Coverage.py warning: No data was collected. (no-data-collected)
​
​
----------------------------------------- generated html file: file://M:\py_tests\report\report.html ---------------------------------------------------- coverage: platform win32, python 3.6.2-final-0 -----------
Name                          Stmts   Miss  Cover
-------------------------------------------------
scripts\demo1.py                  4      4     0%
scripts\test_allure_case.py       7      7     0%
scripts\test_case_01.py          15      2    87%
-------------------------------------------------
TOTAL                            26     13    50%
​
​
Results (2.58s):
       3 passed
       1 skipped

View Code

 

更多插件参考:https://zhuanlan.zhihu.com/p/50317866

常见问题#

有的时候,在pytest.ini中配置了pytest-htmlallure插件之后,执行后报错:

img

出现了这个报错,检查你配置的解释器中是否存在pytest-htmlallure-pytest这两个模块。如果是使用的pycharm ide,那么你除了检查settings中的解释器配置之外,还需要保证运行脚本的编辑器配置是否跟settings中配置一致。 img


 

 

更多插件参考:https://zhuanlan.zhihu.com/p/50317866

 

原文地址:http://www.cnblogs.com/erhuoyuan/p/16797878.html

1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长! 2. 分享目的仅供大家学习和交流,请务用于商业用途! 3. 如果你也有好源码或者教程,可以到用户中心发布,分享有积分奖励和额外收入! 4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解! 5. 如有链接无法下载、失效或广告,请联系管理员处理! 6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需! 7. 如遇到加密压缩包,默认解压密码为"gltf",如遇到无法解压的请联系管理员! 8. 因为资源和程序源码均为可复制品,所以不支持任何理由的退款兑现,请斟酌后支付下载 声明:如果标题没有注明"已测试"或者"测试可用"等字样的资源源码均未经过站长测试.特别注意没有标注的源码不保证任何可用性