1、pytest简介

pytest是一个成熟的python单元测试框架,可以和selenium、requests、appium等结合实现web、接口及app自动化。能够实现用例管理、执行、跳过、失败重跑等功能;结合allure,能够生成测试报告。pytest常用的插件如下:

pytest-ordering:调整用例执行顺序;pytest-rerunfailures:设置用例失败重跑;pytest-xdist:分布式执行测试用例;pytest-html:生成html格式的测试用例;allure-pytest:生成测试报告。

pytest安装:pip install pytest

安装完成后可以用pytest --version 验证是否安装成功,如果返回版本号,则表示已成功安装。

2、pytest命名规则

测试用例文件(模块):以_test开头或以_test结尾;测试类:以Test开头,不能有构造函数;测试函数:以test开头;

3、pytest用例运行方式

pytest用例运行方式共有3种,分别是命令行模式、主函数模式和读取配置文件运行。

3.1、命令行模式

以如下测试套为例:

本案例中建了2个测试套,分别为interface_testcase和page_testcase。其中page_testcase下有2个模块;interface_testcase下有一个模块;各个模块用例如下:

test_register.py:

import pytest

class TestRegister:

def test_register(self):

print('register01_测试')

test_login.py:

import pytest

class TestLogin:

def test_login01(self):

print('login01_测试')

def test_login02(self):

print('login02_测试')

def test_login03(self):

print('login03_测试')

def test_login04(self):

print('login04_测试')

test_query.py:

import pytest

class TestQuery:

def test_register(self):

print('query01_测试')

如上,在3个模块中共新建了6个测试用例。

在终端中,直接输入pytest,可以看到6个用例全部执行通过:

命令行运行时,可以指定选项和参数,比如想要指定运行目录,输出详细的运行结果(-v)并打印调试信息(-s),执行如下:

pytest -vs .\page_testcase\test_login.py::TestLogin::test_login03

可以看到,只运行了test_login.py中TestLogin类里的test_login03方法,且打印信息比不带参数更加详细。

3.2、主函数模式

首先需要在项目根目录下新建一个运行py文件(文件名自定义),内容如下:

import pytest

if __name__ == "__main__":

pytest.main(['-vs', './page_testcase/test_login.py::TestLogin::test_login03'])

# pytest.main()参数说明:

'''

def main(

args: Optional[Union[List[str], "os.PathLike[str]"]] = None,

plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None,

)

args为运行参数,同命令行,可传入测试用例路径;

plugins为插件参数,也是列表

'''

运行结果如下:

可以看到,运行效果和命令行完全一致。

3.3、读取pytest.ini配置文件

该方式是最常用的运行方式,首先需要在项目根目录下新建pytest.ini(必须为这个文件名),编码为ANSI,配置好pytest.ini后,再用命令行或主函数运行都会先读取该配置文件中配置的参数,常用的参数如下:

addops # 命令行参数,用空格分隔

testpaths # 测试用例路径

python_files # 模块名的规则(可以自定义规则,定义之后模块名需要按照配置文件中进行命名)

python_classes # 类名命名规则

python_functions # 方法命名规则

markers # 标记参数,以key:value的方式赋值

配置文件示例:

[pytest]

addopts = -vs --html ./report/report.html --reruns 2 -k "login"

testpaths = ./

python_files = test_*.py

python_classes = Test*

python_functions = test*

markers =

smoke:冒烟用例

mode1:模块1

mode2:模块2

在命令行执行:pytest --collect-only,可以看到有哪些用例被选中(不会执行):

运行时常用的参数如下:

-s 输出调试信息,包括print打印的信息

-v 显示详细运行信息

-n 分布式执行(如:-n 2表示启动2个线程执行用例)

-x 表示有用例失败时,停止执行

--maxfail=5 表示失败5个用例,则停止执行

-k 根据参数后的关键字指定测试用例(如:-k "abc"表示执行带abc的测试用例)

--html 生成html格式的测试报告(如:--html ./report/report.html)

-h 显示帮助信息和参数列表

--collect-only 查看当前配置下,哪些用例会被执行,一般在测试运行前做检查

-m 标记测试用例并分组

-lf debug最后一个失败(--last-failed)的测试用例

-q 和-v相反,简化输出信息

--reruns NUM 用例失败后重新执行NUM次

4、pytest用例执行设置

4.1、执行顺序

默认从上到下,要改变执行顺序可以使用mark标记,如:

import pytest

class TestLogin:

@pytest.mark.run(order=4)

def test_login01(self):

print('login01_测试')

@pytest.mark.run(order=3)

def test_login02(self):

print('login02_测试')

@pytest.mark.run(order=2)

def test_login03(self):

print('login03_测试')

@pytest.mark.run(order=1)

def test_login04(self):

print('login04_测试')

执行结果如下:

4.2、分组执行

用例标记:标记用例类型,即配置文件中配置的markers,如: #test_login.py

import pytest

class TestLogin:

@pytest.mark.run(order=4)

def test_login01(self):

print('login01_测试')

@pytest.mark.smoke #标记用例为冒烟用例

@pytest.mark.run(order=3)

def test_login02(self):

print('login02_测试')

@pytest.mark.run(order=2)

def test_login03(self):

print('login03_测试')

@pytest.mark.smoke

@pytest.mark.run(order=1)

def test_login04(self):

print('login04_测试')

#test_register.py

import pytest

class TestRegister:

@pytest.mark.mode1 #标记用例为mode1

def test_register(self):

print('register01_测试')

分组执行 如需执行smoke和mode1标记的用例,方式如下: pytest -vs -m "smoke or mode1"

执行结果如下:

4.3、跳过用例

跳过用例有两种方式,一种是不设置跳过条件,直接跳过,如:

import pytest

class TestLogin:

@pytest.mark.run(order=4)

def test_login01(self):

print('login01_测试')

@pytest.mark.smoke

@pytest.mark.run(order=3)

@pytest.mark.skip(reason="不执行该用例") #跳过该用例

def test_login02(self):

print('login02_测试')

@pytest.mark.run(order=2)

def test_login03(self):

print('login03_测试')

@pytest.mark.smoke

@pytest.mark.run(order=1)

def test_login04(self):

print('login04_测试')

执行时该用例会自动跳过:

另一种是有条件跳过,如:

import pytest

class TestLogin:

level =2

@pytest.mark.run(order=4)

def test_login01(self):

print('login01_测试')

@pytest.mark.smoke

@pytest.mark.run(order=3)

@pytest.mark.skipif(level>=2,reason="lv>=2,不执行该用例")

def test_login02(self):

print('login02_测试')

@pytest.mark.skipif(level>2, reason="lv>2,不执行该用例")

@pytest.mark.run(order=2)

def test_login03(self):

print('login03_测试')

@pytest.mark.smoke

@pytest.mark.run(order=1)

def test_login04(self):

print('login04_测试')

可以看到,test_login03未满足跳过条件,未跳过执行:

4.4、失败重跑

用例如下:

import pytest

class TestRegister:

@pytest.mark.mode1

def test_register(self):

print('register01_测试')

assert 1 != 1 # 断言失败

执行如下:

pytest --reruns=3 .\page_testcase\test_register.py

结果如下,可以看到一共执行了4次:

page_testcase/test_register.py::TestRegister::test_register register01_测试

RERUN

page_testcase/test_register.py::TestRegister::test_register register01_测试

RERUN

page_testcase/test_register.py::TestRegister::test_register register01_测试

RERUN

page_testcase/test_register.py::TestRegister::test_register register01_测试

FAILED

5、固件设置

5.1、setup/teardown

每个用例执行之前和之后要执行的代码,示例如下:

# test_register.py

import pytest

class TestRegister:

def setup(self):

print("\n在执行测试用例前执行该代码")

@pytest.mark.smoke

def test_register01(self):

print('register01_测试')

def test_register02(self):

print('register02_测试')

def teardown(self):

print("\n在执行测试用例后执行该代码")

执行设置,设置为只执行smoke用例:

pytest -m "smoke" .\page_testcase\test_register.py

执行结果,可以看到setup和teardown未设置为smoke,依然被执行:

在执行测试用例前执行该代码

register01_测试

PASSED

在执行测试用例后执行该代码

5.2、setup_class/teardown_class

在所有用例执行前/后执行一次

5.3、@pytest.fixture()装饰器

前两种方式只能完成全局的固件设置,如果需要对特定用例生效,则可利用@pytest.fixture()装饰器来实现。

装饰器语法如下:

pytest.fixture(scope="", params="", autouse="", ids="", name="")

# scope:表示被@pytest.fixture标记的方法的作用域,function(默认),class,module,package/session

# params:参数化,支持[],(),[{},{}...],({},{}...)

# autouse:为True表示自动使用前后置,默认false

# ids:当使用params参数化时,给每个值设置变量名

# name:起了别名后,原名称不可再使用

参数化示例:

# test_register.py

import pytest

@pytest.fixture(scope="function", params=['aaa', 'bbb', 'ccc'], ids=['a', 'b', 'c'])

def my_fixture(request):

print("\ntest start")

yield request.param

print("\ntest end")

class TestRegister():

def test_register01(self):

print('\nregister01_测试')

def test_register02(self):

print('\nregister02_测试')

def test_register03(self, my_fixture):

print('\nregister03_测试')

print(my_fixture)

运行结果如下,传递了3个参数,test_register03运行了3次:

test_register.py::TestRegister::test_register01

register01_测试

PASSED

test_register.py::TestRegister::test_register02

register02_测试

PASSED

test_register.py::TestRegister::test_register03[a]

test start

register03_测试

aaa

PASSED

test end

test_register.py::TestRegister::test_register03[b]

test start

register03_测试

bbb

PASSED

test end

test_register.py::TestRegister::test_register03[c]

test start

register03_测试

ccc

PASSED

test end

5.4、conftest.py配置文件

配置conftest.py可以在不同的py文件中使用同一个fixture函数,且无需import导入。

如,在页面测试中,分了注册、登录测试,注册/登录可以分别使用各自的conftest.py配置文件,还可以使用全局的conftest.py文件,目录结构如下所示:

代码如下:

全局conftest.py:

# page_testcase/conftest.py

import pytest

@pytest.fixture(scope="function")

def page_fixture(request):

print("\npage start")

yield

print("\npage end")

login模块:

# login/test_login.py

import pytest

class TestLogin:

def test_login01(self):

print('login01_测试')

def test_login02(self):

print('login02_测试')

def test_login03(self):

print('login03_测试')

def test_login04(self):

print('login04_测试')

# login/conftest.py

import pytest

@pytest.fixture(scope="function", params=['aaa', 'bbb'], autouse=True)

def login_fixture(request):

print("\nlogin start")

yield request.param

print("\nlogin end")

register模块:

# register/test_register.py

import pytest

class TestRegister():

def test_register01(self):

print('\nregister01_测试')

def test_register02(self):

print('\nregister02_测试')

def test_register03(self, page_fixture, register_fixture):

print('\nregister03_测试')

print(register_fixture)

# register/config.py

import pytest

@pytest.fixture(scope="function", params=['ccc', 'ddd'])

def register_fixture(request):

print("\nregister start")

yield request.param

print("\nregister end")

执行函数:

import pytest

if __name__ == "__main__":

pytest.main(['-vs', './page_testcase'])

执行结果如下:

collecting ... collected 12 items

page_testcase/login/test_login.py::TestLogin::test_login01[aaa]

login start

login01_测试

PASSED

login end

page_testcase/login/test_login.py::TestLogin::test_login01[bbb]

login start

login01_测试

PASSED

login end

page_testcase/login/test_login.py::TestLogin::test_login02[aaa]

login start

login02_测试

PASSED

login end

page_testcase/login/test_login.py::TestLogin::test_login02[bbb]

login start

login02_测试

PASSED

login end

page_testcase/login/test_login.py::TestLogin::test_login03[aaa]

login start

login03_测试

PASSED

login end

page_testcase/login/test_login.py::TestLogin::test_login03[bbb]

login start

login03_测试

PASSED

login end

page_testcase/login/test_login.py::TestLogin::test_login04[aaa]

login start

login04_测试

PASSED

login end

page_testcase/login/test_login.py::TestLogin::test_login04[bbb]

login start

login04_测试

PASSED

login end

page_testcase/register/test_register.py::TestRegister::test_register01

register01_测试

PASSED

page_testcase/register/test_register.py::TestRegister::test_register02

register02_测试

PASSED

page_testcase/register/test_register.py::TestRegister::test_register03[ccc]

page start

register start

register03_测试

ccc

PASSED

register end

page end

page_testcase/register/test_register.py::TestRegister::test_register03[ddd]

page start

register start

register03_测试

ddd

PASSED

register end

page end

结果说明:

登录模块中共4个用例,使用了模块自带的conftest.py,autouse=True,所以每个用例执行时,都会带上固件设置,即在用例开始时打印login start,结束时答应login end,且每个用例接收两个参数,所以用例共执行8次;

注册模块共3个用例,其中第三个使用了外层的page_fixture固件和内层register_fixture固件,所以第三个用例先打印page start,执行内部固件时再打印register start,然后再执行用例本身,然后结束固件。所以第注册模块第1,2个用例执行1次,第3个用例带2个参数执行2次,共执行4次;

按照该方式运行,用例1共执行12次。

6、测试报告

6.1、pytest-html

生成html格式的报告,pytest.ini中配置如下:

addopts = -vs --html ./report/report.html

生成的报告格式如下:

6.2、allure-pytest

下载包:下载地址:https://github.com/allure-framework/allure2/releases 解压后将bin目录配置到环境变量:系统变量->Path 验证是否安装成功:allure --version,能查到版本即安装成功 生成json格式的临时报告:在pytest.ini中配置addopts = -vs --alluredir ./temp 生成allure报告,如下图所示:

精彩内容

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: