天天看点

单元测试框架--pytest初识pytest认知pytest第一个简单例子pytest 使用方法pytest扩展

pytest

  • pytest认知
  • pytest第一个简单例子
  • pytest 使用方法
    • 断言
    • Fixture
    • 参数化
    • 运行测试
      • 查看帮助
      • 运行名称中包含某字符串的测试用例
      • 减少测试的运行冗长
      • 如果出现一条测试用例失败,则退出测试
      • 运行测试目录
      • 指定特定类或方法执行
      • 通过main()方法运行测试
      • 运行所有用mark修饰的测试用例
      • 生成测试报告
  • pytest扩展
    • pytest-html
    • pytest-rerunfailures
    • pytest-parallel扩展

pytest认知

pytest是Python的一个第三方单元测试框架,提供了更加丰富的扩展,更加简单、灵活,弥补了unittest在做web自动化测试的一些不足。

pytest支持pip安装

pip install -U pytest
           

查看pytest版本

pytest --version
           

对于pytest学习,可以参考:

  • 官方文档
  • pytest中文文档
  • pytest – 中文文档

pytest第一个简单例子

  1. 首先通过pytest编写一个简单的测试例子,test_sample.py
def inc(x):
    return x + 1

def test_answer():
    assert inc(3) == 5
           
  1. 切换到测试用例目录下,执行【pytest】命令
(py3_heima) D:\zhenghou\python_learning>cd test_pytest

(py3_heima) D:\zhenghou\python_learning\test_pytest>pytest
=============================================================================== test session starts ===============================================================================
platform win32 -- Python 3.6.4, pytest-5.4.3, py-1.8.2, pluggy-0.13.1
rootdir: D:\zhenghou\python_learning\test_pytest
collected 1 item                                                                                                                                                                   

test_sample.py F                                                                                                                                                             [100%]

==================================================================================== FAILURES =====================================================================================
___________________________________________________________________________________ test_answer ___________________________________________________________________________________

    def test_answer():
>       assert inc(3) == 5
E       assert 4 == 5
E        +  where 4 = inc(3)

test_sample.py:13: AssertionError
============================================================================= short test summary info =============================================================================
FAILED test_sample.py::test_answer - assert 4 == 5
================================================================================ 1 failed in 0.46s ================================================================================
           

  • pytest更加简单,不需要想unittest一样必须创建测试类
  • 使用assert断言
  • pytest的测试文件和测试函数必须以【test】开头

此外,pytest也可以使用像unittest一样,通过main()方法执行测试用例

import pytest

def inc(x):
    return x + 1

def test_answer():
    assert inc(3) == 5


if __name__ == "__main__":
    pytest.main()
           

在一个类中执行多组测试

class TestClass:
    def test_one(self):
        x = "this"
        assert "h" in x

    def test_two(self):
        x = "hello"
        assert hasattr(x, "check")
           

进入test_class所在目录,执行【test_class.py】

(py3_heima) D:\zhenghou\python_learning\test_pytest>pytest -q test_class.py
.F                                                                                                                                                                           [100%]
==================================================================================== FAILURES =====================================================================================
_______________________________________________________________________________ TestClass.test_two ________________________________________________________________________________

self = <test_pytest.test_class.TestClass object at 0x000002D1ACA22780>

    def test_two(self):
        x = "hello"
>       assert hasattr(x, "check")
E       AssertionError: assert False
E        +  where False = hasattr('hello', 'check')

test_class.py:16: AssertionError
============================================================================= short test summary info =============================================================================
FAILED test_class.py::TestClass::test_two - AssertionError: assert False
1 failed, 1 passed in 0.38s

           

pytest 使用方法

断言

pytest单元测试框架没有提供专门的断言方法,而是直接使用Python的asser进行断言。

# 计算a + b
def add(a, b):
    return a + b

# 判断是否为素数
def is_prime(n):
    if n <= 1:
        return False
    else:
        for i in range(2, n):
            if n % i == 0:
                return False
        return True


# 测试相等
def test_add_1():
    assert add(3, 4) == 7

# 测试不相等
def test_add_2():
    assert add(5, 8) != 12

# 测试小于等于
def test_add_3():
    assert add(5, 8) <= 10

# 测试大于等于
def test_add_4():
    assert add(4, 5) >= 3

# 测试包含于
def test_in():
    assert "h" in "hello"

# 测试不包含
def test_not_in():
    assert "he" not in "hello"

# 测试是否为True
def test_true_1():
    assert is_prime(1)

# 测试是否为True
def test_true_2():
    assert is_prime(1) is True

# 测试是否为True
def test_true_3():
    assert is_prime(1) is not True

# 测试是否为False
def test_false():
    assert is_prime(1) is False
           

Fixture

Fixture通常用来对测试方法、测试函数、测试类和整个测试文件进行初始化或还原测试环境

# 功能函数:计算两个数相加
def add(a, b):
    return a + b

# ==============fixture=======================
def setup_module(module):
    print("setup_module=========================>")

def teardown_module(module):
    print("teardown_module==========================>")

def setup_function(function):
    print("setup_function=====================>")

def teardown_function(function):
    print("teardown_function===========================>")

def setup():
    print("setup=================================>")

def teardown():
    print("teardown=================================>")


# 测试用例
def test_add_2_1():
    assert add(2, 1) == 3

def test_add_gg_aa():
    assert add("gg", "aa") == "ag"
           
  • setup_module/teardown_module: 在当前文件中,在所有测试用例执行之前与之后执行
  • setup_function/teardown_function: 在每个测试函数之前与之后执行
  • setup/teardown: 在每个测试函数之前与之后执行。

测试类

# 功能函数:计算两个数相加
def add(a, b):
    return a + b

class TestAdd:
    @classmethod
    def setup_class(cls):
        print("setup_class======================>")

    @classmethod
    def teardown_class(cls):
        print("teardown_class====================>")

    def setup_method(self, method):
        print("setup_method==================>")

    def teardown_method(self, method):
        print("teardown_method==========================>")

    def setup(self):
        print("setup=====================>")

    def teardown(self):
        print("teardown=============================>")

    # 测试用例
    def test_number_3_4(self):
        print("test add number 3 and 4")
        assert add(3, 4) == 7

    def test_string_gg_aa(self):
        print("test add string gg and aa")
        assert add("gg", "aa") == "ga"

           
  • setup_class/teardown_class: 在当前测试类的开始与结束时执行
  • setup_method/teardown_method: 在每个测试方法开始与结束执行
  • setup/teardown: 在每个测试方法开始与结束时执行,同样可以用于测试函数

参数化

当一组测试用例有固定的测试数据是,可以通过参数化的方式简化测试用例书写。

import pytest
import math


@pytest.mark.parametrize(
    "base, exponent, expected",
    [(2, 2, 4),
     (2, 3, 8),
     (1, 9, 1),
     (0, 9, 0)],
    ids=["case1", "case2", "case3", "case4"]
)
def test_pow(base, exponent, expected):
    assert math.pow(base, exponent) == expected
           
  • "base, exponent, expected"

    : 用于定义参数的名称
  • ids

    : 默认为None,用于定义测试用例的名称

执行结果

(py3_heima) D:\zhenghou\python_learning\test_pytest>pytest -v test_parametrize.py
=============================================================================== test session starts ===============================================================================
platform win32 -- Python 3.6.4, pytest-5.4.3, py-1.8.2, pluggy-0.13.1 -- d:\env_director\envs\py3_heima\scripts\python.exe
cachedir: .pytest_cache
rootdir: D:\zhenghou\python_learning\test_pytest
collected 4 items                                                                                                                                                                  

test_parametrize.py::test_pow[case1] PASSED                                                                                                                                  [ 25%]
test_parametrize.py::test_pow[case2] PASSED                                                                                                                                  [ 50%]
test_parametrize.py::test_pow[case3] PASSED                                                                                                                                  [ 75%]
test_parametrize.py::test_pow[case4] PASSED                                                                                                                                  [100%]

================================================================================ 4 passed in 0.16s ================================================================================
           

运行测试

您可以从命令行通过python解释器调用测试:

python -m pytest [...]
           

查看帮助

运行名称中包含某字符串的测试用例

-k EXPRESSION         only run tests which match the given substring expression. An expression is a python evaluatable
                        expression where all names are substring-matched against test names and their parent classes.
                        Example: -k 'test_method or test_other' matches all test functions and classes whose name
                        contains 'test_method' or 'test_other', while -k 'not test_method' matches those that don't
                        contain 'test_method' in their names. -k 'not test_method and not test_other' will eliminate the
                        matches. Additionally keywords are matched to classes and functions containing extra names in
                        their 'extra_keyword_matches' set, as well as functions which have names assigned directly to
                        them. The matching is case-insensitive.
           

使用

-k

指定名称中包含某字符串的测试用例。

pytest -k add test_assert.py
           

执行【test_assert.py】

(py3_heima) D:\zhenghou\python_learning\test_pytest>pytest -k add  -v test_assert.py
=============================================================================== test session starts ===============================================================================
platform win32 -- Python 3.6.4, pytest-5.4.3, py-1.8.2, pluggy-0.13.1 -- d:\env_director\envs\py3_heima\scripts\python.exe
cachedir: .pytest_cache
rootdir: D:\zhenghou\python_learning\test_pytest
collected 10 items / 6 deselected / 4 selected                                                                                                                                     

test_assert.py::test_add_1 PASSED                                                                                                                                            [ 25%]
test_assert.py::test_add_2 PASSED                                                                                                                                            [ 50%]
test_assert.py::test_add_3 FAILED                                                                                                                                            [ 75%]
test_assert.py::test_add_4 PASSED                                                                                                                                            [100%]

==================================================================================== FAILURES =====================================================================================
___________________________________________________________________________________ test_add_3 ____________________________________________________________________________________

    def test_add_3():
>       assert add(5, 8) <= 10
E       assert 13 <= 10
E        +  where 13 = add(5, 8)

test_assert.py:33: AssertionError
============================================================================= short test summary info =============================================================================
FAILED test_assert.py::test_add_3 - assert 13 <= 10
==================================================================== 1 failed, 3 passed, 6 deselected in 0.24s ====================================================================
           

减少测试的运行冗长

(py3_heima) D:\zhenghou\python_learning\test_pytest>pytest -q test_assert.py
..F..FFF..                                                                                                                                                                   [100%]
==================================================================================== FAILURES =====================================================================================
___________________________________________________________________________________ test_add_3 ____________________________________________________________________________________

    def test_add_3():
>       assert add(5, 8) <= 10
E       assert 13 <= 10
E        +  where 13 = add(5, 8)

test_assert.py:33: AssertionError
___________________________________________________________________________________ test_not_in ___________________________________________________________________________________

    def test_not_in():
>       assert "he" not in "hello"
E       AssertionError: assert 'he' not in 'hello'
E         'he' is contained here:
E           hello
E         ? ++

test_assert.py:45: AssertionError
___________________________________________________________________________________ test_true_1 ___________________________________________________________________________________

    def test_true_1():
>       assert is_prime(1)
E       assert False
E        +  where False = is_prime(1)

test_assert.py:49: AssertionError
___________________________________________________________________________________ test_true_2 ___________________________________________________________________________________

    def test_true_2():
>       assert is_prime(1) is True
E       assert False is True
E        +  where False = is_prime(1)

test_assert.py:53: AssertionError
============================================================================= short test summary info =============================================================================
FAILED test_assert.py::test_add_3 - assert 13 <= 10
FAILED test_assert.py::test_not_in - AssertionError: assert 'he' not in 'hello'
FAILED test_assert.py::test_true_1 - assert False
FAILED test_assert.py::test_true_2 - assert False is True
4 failed, 6 passed in 0.27s
           

如果出现一条测试用例失败,则退出测试

使用

-x

执行测试用例

(py3_heima) D:\zhenghou\python_learning\test_pytest>pytest -x test_assert.py
=============================================================================== test session starts ===============================================================================
platform win32 -- Python 3.6.4, pytest-5.4.3, py-1.8.2, pluggy-0.13.1
rootdir: D:\zhenghou\python_learning\test_pytest
collected 10 items                                                                                                                                                                 

test_assert.py ..F

==================================================================================== FAILURES =====================================================================================
___________________________________________________________________________________ test_add_3 ____________________________________________________________________________________

    def test_add_3():
>       assert add(5, 8) <= 10
E       assert 13 <= 10
E        +  where 13 = add(5, 8)

test_assert.py:33: AssertionError
============================================================================= short test summary info =============================================================================
FAILED test_assert.py::test_add_3 - assert 13 <= 10
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
=========================================================================== 1 failed, 2 passed in 0.21s ===========================================================================
           

pytest --maxfail=2

 --maxfail=num         exit after first num failures or errors.
           

运行测试目录

测试目录既可以指定相对路径,也可以使用绝对路径

(py3_heima) D:\zhenghou\python_learning>pytest ./test_pytest
=============================================================================== test session starts ===============================================================================
platform win32 -- Python 3.6.4, pytest-5.4.3, py-1.8.2, pluggy-0.13.1
rootdir: D:\zhenghou\python_learning
collected 19 items                                                                                                                                                                 

test_pytest\test_assert.py ..F..FFF..                                                                                                                                        [ 52%]
test_pytest\test_class.py .F                                                                                                                                                 [ 63%]
test_pytest\test_fixture.py .F                                                                                                                                               [ 73%]
test_pytest\test_parametrize.py ....                                                                                                                                         [ 94%]
test_pytest\test_sample.py F                                                                                                                                                 [100%]

==================================================================================== FAILURES =====================================================================================
___________________________________________________________________________________ test_add_3 ____________________________________________________________________________________

    def test_add_3():
>       assert add(5, 8) <= 10
E       assert 13 <= 10
E        +  where 13 = add(5, 8)

test_pytest\test_assert.py:33: AssertionError
___________________________________________________________________________________ test_not_in ___________________________________________________________________________________

    def test_not_in():
>       assert "he" not in "hello"
E       AssertionError: assert 'he' not in 'hello'
E         'he' is contained here:
E           hello
E         ? ++

test_pytest\test_assert.py:45: AssertionError
___________________________________________________________________________________ test_true_1 ___________________________________________________________________________________

    def test_true_1():
>       assert is_prime(1)
E       assert False
E        +  where False = is_prime(1)

test_pytest\test_assert.py:49: AssertionError
___________________________________________________________________________________ test_true_2 ___________________________________________________________________________________

    def test_true_2():
>       assert is_prime(1) is True
E       assert False is True
E        +  where False = is_prime(1)

test_pytest\test_assert.py:53: AssertionError
_______________________________________________________________________________ TestClass.test_two ________________________________________________________________________________

self = <test_pytest.test_class.TestClass object at 0x00000243DBAF00B8>

    def test_two(self):
        x = "hello"
>       assert hasattr(x, "check")
E       AssertionError: assert False
E        +  where False = hasattr('hello', 'check')

test_pytest\test_class.py:16: AssertionError
____________________________________________________________________________ TestAdd.test_string_gg_aa ____________________________________________________________________________

self = <test_pytest.test_fixture.TestAdd object at 0x00000243DBAF0470>

    def test_string_gg_aa(self):
        print("test add string gg and aa")
>       assert add("gg", "aa") == "ga"
E       AssertionError: assert 'ggaa' == 'ga'
E         - ga
E         + ggaa

test_pytest\test_fixture.py:68: AssertionError
------------------------------------------------------------------------------ Captured stdout setup ------------------------------------------------------------------------------
setup_method==================>
setup=====================>
------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------
test add string gg and aa
---------------------------------------------------------------------------- Captured stdout teardown -----------------------------------------------------------------------------
teardown=============================>
teardown_method==========================>
teardown_class====================>
___________________________________________________________________________________ test_answer ___________________________________________________________________________________

    def test_answer():
>       assert inc(3) == 5
E       assert 4 == 5
E        +  where 4 = inc(3)

test_pytest\test_sample.py:14: AssertionError
============================================================================= short test summary info =============================================================================
FAILED test_pytest/test_assert.py::test_add_3 - assert 13 <= 10
FAILED test_pytest/test_assert.py::test_not_in - AssertionError: assert 'he' not in 'hello'
FAILED test_pytest/test_assert.py::test_true_1 - assert False
FAILED test_pytest/test_assert.py::test_true_2 - assert False is True
FAILED test_pytest/test_class.py::TestClass::test_two - AssertionError: assert False
FAILED test_pytest/test_fixture.py::TestAdd::test_string_gg_aa - AssertionError: assert 'ggaa' == 'ga'
FAILED test_pytest/test_sample.py::test_answer - assert 4 == 5
========================================================================== 7 failed, 12 passed in 0.37s ===========================================================================
           

指定特定类或方法执行

每个收集的测试都被分配一个唯一的 nodeid 它由模块文件名和诸如类名、函数名和参数化参数等说明符组成,用 :: 字符。

在模块内运行特定测试:

pytest test_mod.py::test_func
           

在命令行中指定测试方法的另一个示例:

pytest test_mod.py::TestClass::test_method
           

例如,指定运行test_fixture.py文件中TestAdd类下的test_number_3_4方法

py3_heima) D:\zhenghou\python_learning\test_pytest>pytest test_fixture.py::TestAdd::test_number_3_4
=============================================================================== test session starts ===============================================================================
platform win32 -- Python 3.6.4, pytest-5.4.3, py-1.8.2, pluggy-0.13.1
rootdir: D:\zhenghou\python_learning\test_pytest
collected 1 item                                                                                                                                                                   

test_fixture.py .                                                                                                                                                            [100%]

================================================================================ 1 passed in 0.02s ================================================================================
           

通过main()方法运行测试

import pytest

if __name__ == "__main__":
	pytest.main("-s", "./test_dir")
           

运行所有用mark修饰的测试用例

Run tests by marker expressions

pytest -m slow
           

将运行所有用 @pytest.mark.slow 装饰符。

生成测试报告

  1. 生成JUnit XML文件
(py3_heima) D:\zhenghou\python_learning>pytest ./test_pytest --junit-xml=./report/log.xml
           
单元测试框架--pytest初识pytest认知pytest第一个简单例子pytest 使用方法pytest扩展
  1. 生成在线测试报告

执行上述代码会生成一个session-log链接,使用浏览器打开,会得到一张HTML格式的测试报告

单元测试框架--pytest初识pytest认知pytest第一个简单例子pytest 使用方法pytest扩展

pytest扩展

pytest-html

pytest-html可以生成HTML格式的测试报告,还支持测试用例失败截图,对于web自动化测试来说非常有用。

安装

pip install pytest-html
           

运行测试用例

(py3_heima) D:\zhenghou\python_learning>pytest ./test_pytest --html=./report/result.html
           

执行结果

单元测试框架--pytest初识pytest认知pytest第一个简单例子pytest 使用方法pytest扩展

pytest-rerunfailures

pytest-rerunfailures可以在测试用例失败时进行重试

安装

pip install pytest-rerunfailures
           

安装完成,通过【–reruns】参数设置测试用例运行失败后的重试次数

(py3_heima) D:\zhenghou\python_learning\test_pytest>pytest -v test_sample.py --reruns 3
=============================================================================== test session starts ===============================================================================
platform win32 -- Python 3.6.4, pytest-5.4.3, py-1.8.2, pluggy-0.13.1 -- d:\env_director\envs\py3_heima\scripts\python.exe
cachedir: .pytest_cache
metadata: {'Python': '3.6.4', 'Platform': 'Windows-10-10.0.16299-SP0', 'Packages': {'pytest': '5.4.3', 'py': '1.8.2', 'pluggy': '0.13.1'}, 'Plugins': {'html': '2.1.1', 'metadata':
'1.9.0', 'rerunfailures': '9.0'}, 'JAVA_HOME': 'F:\\java8'}
rootdir: D:\zhenghou\python_learning\test_pytest
plugins: html-2.1.1, metadata-1.9.0, rerunfailures-9.0
collected 1 item                                                                                                                                                                   

test_sample.py::test_answer RERUN                                                                                                                                            [100%]
test_sample.py::test_answer RERUN                                                                                                                                            [100%]
test_sample.py::test_answer RERUN                                                                                                                                            [100%]
test_sample.py::test_answer FAILED                                                                                                                                           [100%]

==================================================================================== FAILURES =====================================================================================
___________________________________________________________________________________ test_answer ___________________________________________________________________________________

    def test_answer():
>       assert inc(3) == 5
E       assert 4 == 5
E         +4
E         -5

test_sample.py:14: AssertionError
============================================================================= short test summary info =============================================================================
FAILED test_sample.py::test_answer - assert 4 == 5
=========================================================================== 1 failed, 3 rerun in 0.09s ============================================================================
           

pytest-parallel扩展

pytest-parallel扩展可以实现测试用例的并行运行

安装

pip install pytest-parallel
           

创建测试用例

import time


def test_01():
    time.sleep(3)

def test_02():
    time.sleep(5)

def test_03():
    time.sleep(6)
           

不使用线程执行测试用例

(py3_heima) D:\zhenghou\python_learning\test_pytest>pytest -q test_parallel.py
...                                                                                                                                                                          [100%]
3 passed in 14.18s
           

使用【–test-per-worker】指定线程数,【auto】为自动分配

pytest -q test_parallel.py --tests-per-worker auto
           

其他用法

pytest --workers 2  # run 2 workers with 1 test per worker at a time
pytest --workers auto  # run 4 workers(4核) with 1 test per worker 
pytest --tests-per-worker 4  # run 1 worker with 4 tests at a time 

pytest --tests-per-worker auto  # runs 1 worker with up to 50 test2 at a time

pytest --workers 2 --tests-per-worker auto
           

而pytest-parallel支持python3.6及以上版本,如果是想做多进程并发的需要在linux平台或mac上做,windows上不起作用即(workers永远=1),如果是做多线程的Linux/Mac/Windows平台都支持,进程数为workers设置的值

参考:python-pytest使用(4)-多线程多进程运行