PyTest

文章目录

    • 一 pytest单元测试框架
      • 1.1 什么是单元测试框架
      • 1.2 单元测试框架
      • 1.3 单元测试框架主要做什么
    • 二 单元测试框架和自动化测试框架有什么关系?
      • 2.1 什么是自动化测试框架以及作用是什么
      • 2.2 pytest单元测试框架和自动化测试框架的关系
    • 三 pytest简介
    • 四 使用 pytest,默认的测试用例规则以及基础应用
    • 五 pytest测试用例的运行方式
      • 5.1 主函数模式
      • 5.2 命令行模式
      • 5.3 案例实战
      • 5.4 使用pytest.ini配置文件运行(最常用)
    • 六 pytest测试用例的执行顺序
    • 七 如何分组执行(冒烟、分模块执行、分接口和web执行)
      • 7.1 冒烟测试案例
      • 7.2 分模块执行测试案例
      • 7.3 跳过测试用例
      • 7.4 输出HTML测试报告
    • 八 Pytest框架实现一些前后置(固件、夹具)的处理,常用三种
      • 8.1 setuop/teqrdown/setup_class/teardown_class 所有
      • 8.2 使用 @pytest.fixture() 装饰器来实现部分用例前后置
      • 8.3 通过 conftest.py 和 @pytest.fixture() 结合使用全局的前置应用(比如:项目的全局登录,模块的全局处理)
      • 8.4 断言
      • 8.5 pytest结合 allure-pytest 插件生成 allure 测试报告
      • 8.6 @pytest.mark.parametrize() 基本用法
      • 8.7 YAML文件详解--实现接口自动化

一 pytest单元测试框架

1.1 什么是单元测试框架

单元测试框架是指在软件开发当中,针对软件的最小单位(函数、方法)进行正确性的检查测试。

1.2 单元测试框架

java: junit 和 pytest

pytest: unittest 和 pytest

1.3 单元测试框架主要做什么

  • 测试发现:从对个文件里面去找到我们的测试用例
  • 测试执行:按照一定的顺序和规则去执行,并生成结果
  • 测试判断:通过断言判断预期结果和实际结果的差异
  • 测试报告:统计测试进度,耗时、通过率,生成测试报告

二 单元测试框架和自动化测试框架有什么关系?

2.1 什么是自动化测试框架以及作用是什么

  • 提高测试效率,降低维护成本

  • 减少人工干预、提高测试的准确性,增加代码的重用性

  • 核心思想是让不懂代码的人也能够通过这个框架去实现自动化测试

2.2 pytest单元测试框架和自动化测试框架的关系

单元测试框架:只是自动化测试框架的组成部分之一

三 pytest简介

1、pytest是一个非常成熟的python单元测试框架,比unittest更灵活、容易上手

2、pytest 可以和selenium、request、appium结合实现web自动化、接口自动化、app自动化

3、pytest可以实现测试用例的跳过以及 reruns失败用例重试

4、pytest可以和allure生成非常美观的测试报告

5、pytest可以Jenkins持续集成

5、pytest有很多非常强大的插件、并且这些插件能够实现很多的实用的操作

  • pytest
  • pytest.html(生成html格式的自动化测试报告)
  • pytest-xdist(测试用例分布式执行,多CPU分发)
  • pytest–ordering(用于改变测试用例的执行顺序)
  • pytest-rerunfailures(用于失败后重跑用例)
  • allure-pytest(用于生成美观的测试报告)

一次性安装上面插件的方法:

将这些插件的名称放在 requirements.txt 文件中,使用命令 pip install -r requirements.txt 安装即可:

pytest
pytest-html
pytest-xdist
pytest-ordering
pytest-rerunfailures
allure-pytest

四 使用 pytest,默认的测试用例规则以及基础应用

1、模块名必须以 test_ 开头或者 _test 结尾

2、测试类必须以 Test 开头,并且不能含有 __init__ 方法

3、测试方法必须以test开头

五 pytest测试用例的运行方式

5.1 主函数模式

  • 运行所有:pytest.main()
  • 指定模块:pytest.main(['-vs', 'test-login.py'])
  • 指定目录:pytest.main(['-vs', './interface_testcase'])
  • 通过 nodeid 指定用例运行:nodeid 由模块名,分隔符,类名,方法名和函数名组成

5.2 命令行模式

  • 运行所有:pytest
  • 指定模块:pytest -vs test_login.py
  • 指定目录:pytest -vs ./interface_testcase
  • 参数详解:
    • -s:表示输出调试信息,包括print打印的信息
    • -v:显示更详细的信息
    • -vs:这两个参数一起使用
    • -n:支持多线程或者分布式运行测试用例
      • pytest -vs ./testcase -n 2
    • –reruns num:失败用例重跑,num为重跑的次数
    • -x:只要有一个用例报错,那么测试停止
    • –maxfail=2:出现两个用例失败就停止
    • -k:根据测试用例的部分字符串去指定测试用例

5.3 案例实战

-s 和 -v 参数案例

假如我们现在有一个项目 pytestdemo,所有的用例都存放在 testcase目录中

PyTest_第1张图片

假设登录和登出的测试用例分别如下:

# test_login.py

lass TestLogin:

    def test_login(self):
        print("登录测试")
# test_logout.py

class TestLogout:

    def test_logout(self):
        print("登出测试")

运行所有模块的测试用例

如果要同时运行 test_login.pytest_logout.py 里面的所有测试用例,可以在testcase目录中新建一个 all.py 文件:

# all.py

import pytest

if __name__ == '__main__':
    pytest.main(['-vs'])
  • 参数放在pytest.main(['参数'])里面的列表中
  • -vs 参数表示输出详解的信息,包括测试用例中的print函数的输出信息,输出结果会看到以下内容

PyTest_第2张图片


运行单个模块的测试用例

假设现在只想运行 test_login.py 里面的测试用例,只需在 pytest.main() 添加如下参数即可:

# all.py

import pytest

if __name__ == '__main__':
    pytest.main(['-vs', 'test_login.py'])

运行指定目录的测试用例

假设该项目下有一个专门用于做接口测试的包 interface_testcase,那我如何只运行接口测试里面的测试用例,不运行 testcase 里面的测试用例呢?

PyTest_第3张图片

  • all.py 移动至项目根目录下,并将要运行的目录作为参数传入main函数里面:

    # all.py
    
    import pytest
    
    if __name__ == '__main__':
        pytest.main(['-vs', 'interface_testcase'])
    

    这样就只会运行接口测试的内容了。


通过 nodeid 来指定要执行的测试用例

import pytest

if __name__ == '__main__':
    pytest.main(['-vs', 'interface_testcase/test_interface.py::TestInterface::test_interface'])

-n 参数案例1

运行 testcase 里面的测试用例:

# test_login.py

import time

class TestLogin:

    def test_login(self):
        time.sleep(3)
        print("登录测试")
# test_logout.py

import time

class TestLogout:

    def test_logout(self):
        time.sleep(3)
        print("登出测试")

如果顺序执行的话需要6秒钟:

# all.py

import pytest

if __name__ == '__main__':
    pytest.main(['-vs', 'testcase'])

运行结果:

PyTest_第4张图片

如果是多线程的话,执行时间只需要4.19秒

# all.py

import pytest

if __name__ == '__main__':
    pytest.main(['-vs', 'testcase', '-n=2'])

运行结果:

PyTest_第5张图片


-n 参数案例2

如果 test_login.py 里面有多个测试用例,要多线程执行这些用例应该怎么做呢?

# test_login.py

import time

class TestLogin:

    def test_login(self):
        time.sleep(3)
        print("登录测试")

    def test_username(self):
        time.sleep(3)
        print("用户名测试")

    def test_password(self):
        time.sleep(3)
        print("密码测试")

    def test_verification_code(self):
        time.sleep(3)
        print("验证码测试")

    def test_protocol(self):
        time.sleep(3)
        print("勾选协议测试")
  • 如果是顺序执行,需要花费15秒的时间

    # all.py
    
    import pytest
    
    if __name__ == '__main__':
        pytest.main(['-vs', 'testcase/test_login.py'])
    

    运行结果:

    PyTest_第6张图片

  • 如果是多线程执行,只需要10秒的时间

    # all.py
    
    import pytest
    
    if __name__ == '__main__':
        pytest.main(['-vs', 'testcase/test_login.py', '-n=2'])
    

    运行结果:

    PyTest_第7张图片


–reruns num 参数案例

test_login.py 为例,我们给 test_password 方法设置一个错误的断言

# test_login.py

class TestLogin:

    def test_login(self):         
        print("登录测试")

    def test_username(self):        
        print("用户名测试")

    def test_password(self):        
        print("密码测试")
        assert 1==2

    def test_verification_code(self):        
        print("验证码测试")

    def test_protocol(self):        
        print("勾选协议测试")

修改 all.py 文件:

# all.py

import pytest

if __name__ == '__main__':
    pytest.main(['-vs', 'testcase/test_login.py', '--reruns=2'])

使用主函数模式运行结果:

PyTest_第8张图片

可以看到对于失败的用例,它重跑了2次。

使用命令行模式运行(要先切换到 testcase 目录下)的结果:pytest -vs test_login.py --reruns 2

PyTest_第9张图片


-k 参数 案例

假设现在只想执行 test_login.py 里面包含 user关键字的测试用例,使用命令行模式执行:

pytest -vs test_login.py -k 'user'

执行结果如下:

image-20221004004317041


5.4 使用pytest.ini配置文件运行(最常用)

pytest.ini 是pytest单元测试框架的核心配置文件。

位置:一般放在项目根目录下

编码格式:必须是 ANSI,可以使用 notepad++ 修改编码格式

作用:改变 pytest 默认的行为

运行规则:不管是主函数的模式运行还是命令行模式运行,都会去读取这个配置文件

[pytest]

addopts = -vs			# 命令行的参数,用空格分隔
testpaths = ./testcase	# 测试用例的路径
python_files = test*.py	# 模块名规则
python_classes = Test*	# 类名的规则
python_functions = test	# 方法名的规则

六 pytest测试用例的执行顺序

unittest: ascii的大小来绝对的执行顺序

pytest:默认从上到下顺序执行

案例:

# test_login.py

class TestLogin:

    def test_04_login(self):
        print("登录测试")

    def test_05_username(self):
        print("用户名测试")

    def test_02_password(self):
        print("密码测试")
        assert 1==2

    def test_03_verification_code(self):
        print("验证码测试")

    def test_01_protocol(self):
        print("勾选协议测试")
# all.py

import pytest

if __name__ == '__main__':
    pytest.main(['-vs', 'testcase/test_login.py'])

执行 all.py ,结果如下,可以看出是按用例顺序从上往下执行的:

PyTest_第10张图片


如果不想按从上到下的顺序执行应该怎么做呢?

使用装饰器 @pytest.mark.run(order=num) 来指定用例的执行顺序,其中的 num 给定值为 1、2、3、4…

对于未使用装饰器的来指定的用例,还是依照从上往下的原则:

如下:

import pytest


class TestLogin:

    def test_04_login(self):
        print("登录测试")

    @pytest.mark.run(order=1)
    def test_05_username(self):
        print("用户名测试")

    def test_02_password(self):
        print("密码测试")
        assert 1==2

    @pytest.mark.run(order=2)
    def test_03_verification_code(self):
        print("验证码测试")

    @pytest.mark.run(order=3)
    def test_01_protocol(self):
        print("勾选协议测试")

执行结果如下,可以看到先执行添加了装饰器 @pytest.mark.run(order=num)的用例 test_05_username -> test_03_verification_code -> test_01_protocol:

PyTest_第11张图片

对于未添加装饰器的测试用例 test_04_login 和 test_02_password 按照从上往下的顺序执行。

七 如何分组执行(冒烟、分模块执行、分接口和web执行)

smoke:冒烟用例,分布在各个模块里面

7.1 冒烟测试案例

假设现在不执行全部用例,只想对部分用例做冒烟测试。只需要在需要执行的用例上加装饰器@pytest.mark.smoke进行标记即可。这里的smoke是自己命名的,你命名为其他名字也可以。

下面对 test_login.py 的 test_02_password 和 test_logout.py 的 test_logout 做冒烟测试:

# test_login.py

import pytest


class TestLogin:

    def test_04_login(self):
        print("登录测试")

    @pytest.mark.run(order=1)
    @pytest.mark.usermanage
    def test_05_username(self):
        print("用户名测试")

    @pytest.mark.smoke
    def test_02_password(self):
        print("密码测试")
        # assert 1==2

    @pytest.mark.run(order=2)
    def test_03_verification_code(self):
        print("验证码测试")

    @pytest.mark.run(order=3)
    def test_01_protocol(self):
        print("勾选协议测试")
# test_logout.py

import time
import pytest

class TestLogout:

    @pytest.mark.smoke
    def test_logout(self):
        time.sleep(3)
        print("登出测试")

在 pytest.ini 配置文件中加入 marks = …:

[pytest]

addopts = -vs
testpaths = ./
python_files = test*.py
python_classes = Test*
python_functions = test
markers =
    smoke:冒烟用例
    usermanage:用户管理模块
    productmanage:商品管理模块

使用命令行模式执行:pytest -m 'smoke'

测试结果,只执行了标记为 smoke 的用例:

PyTest_第12张图片


7.2 分模块执行测试案例

还是使用上面的代码,假设现在想执行冒烟测试的用例和用户管理模块的用例。在用户管理模块的用例上加上标记

@pytest.mark.usermanage,即 test_login.py 里的 test_05_username 测试用例。

使用命令行模式执行:pytest -m 'smoke or usermanage'

测试结果,可以看出只运行了标记了 smoke 和 usermanage 的用例:

PyTest_第13张图片


7.3 跳过测试用例

⭐无条件跳过测试用例

假设现在想跳过某个用例,不执行它,只需要在这个用例上加装饰器 @pytest.mark.skip(reason='跳过原因')即可。

现在只对 test_login.py 内的测试用例做测试,所以需要修改第一下 pytest.ini 配置文件:

[pytest]

addopts = -vs
testpaths = ./testcase/test_login.py	# 只测试 test_login.py 文件内的测试用例
python_files = test*.py
python_classes = Test*
python_functions = test
markers =
    smoke:冒烟用例
    usermanage:用户管理模块
    productmanage:商品管理模块

修改 test_login.py :

import pytest


class TestLogin:


    def test_04_login(self):
        print("登录测试")

    @pytest.mark.run(order=1)
    @pytest.mark.usermanage
    def test_05_username(self):
        print("用户名测试")

    @pytest.mark.smoke
    def test_02_password(self):
        print("密码测试")
        # assert 1==2

    @pytest.mark.run(order=2)
    def test_03_verification_code(self):
        print("验证码测试")

    @pytest.mark.run(order=3)
    @pytest.mark.skip(reason='不需要勾选协议,所以跳过')
    def test_01_protocol(self):
        print("勾选协议测试")

使用命令行模式执行:pytest

执行结果,可以看到被标记的用例已经跳过了:

PyTest_第14张图片


⭐有条件跳过测试用例

假设当满足某个条件就跳过某个测试用例不执行。修改 test_login.py 文件,定义一个验证码变量 code 并赋值为 666,假设当值为 666 时,验证码测试的测试用例不执行:

import pytest


class TestLogin:

    code = 666

    def test_04_login(self):
        print("登录测试")

    @pytest.mark.run(order=1)
    @pytest.mark.usermanage
    def test_05_username(self):
        print("用户名测试")

    @pytest.mark.smoke
    def test_02_password(self):
        print("密码测试")
        # assert 1==2

    @pytest.mark.run(order=2)
    @pytest.mark.skipif(code=666)
    def test_03_verification_code(self):
        print("验证码测试")

    @pytest.mark.run(order=3)
    @pytest.mark.skip(reason='不需要勾选协议,所以跳过')
    def test_01_protocol(self):
        print("勾选协议测试")

使用命令行模式执行: pytest

执行结果,可以看到验证码测试的测试用例被跳过了:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z3i6frIB-1665244264349)(C:\Users\17863\AppData\Roaming\Typora\typora-user-images\image-20221004213447669.png)]

7.4 输出HTML测试报告

1)在项目根目录下创建目录 report 用于存放测试报告

2)修改 pytest.ini 配置文件

[pytest]

addopts = -vs --html ./report/report.html	# 测试报告存放路径
testpaths = ./testcase/test_login.py
python_files = test*.py
python_classes = Test*
python_functions = test
markers =
    smoke:冒烟用例
    usermanage:用户管理模块
    productmanage:商品管理模块

3)使用命令行模式执行:python

4)进入report 中查看测试报告

八 Pytest框架实现一些前后置(固件、夹具)的处理,常用三种

8.1 setuop/teqrdown/setup_class/teardown_class 所有

为什么需要这些功能?

比如:web自动化执行用例之前,请问需要打开浏览器吗?用例执行后需要关闭浏览器吗?

class TestFirm:

    # 这个在所有用例之前,只执行一次
    def setup_class(self):
        print('在每个类执行前的初始化工作:比如创建日志对象,创建数据库的连接')

    # 在每个用例之前执行一次
    def setup(self):
        print('\n在执行测试用例之前初始化的代码:打开浏览器,加载网页')

    def test_01_baidu(self):
        print("打开百度")

    def test_02_huawei(self):
        print("打开华为")

    def test_03_xiaomi(self):
        print('打开小米')
        
    def teardown(self):
        print('\n在执行测试用例之后的扫尾代码:关闭浏览器')
        
    def teardown_class(self):
        print('在每个类执行后的扫尾代码:比如,销毁日志对象,销毁数据库连接')

注意:和 unittest 不一样,全是小写。

8.2 使用 @pytest.fixture() 装饰器来实现部分用例前后置

装饰器

@pytest.fixture(scope=‘’, params=‘’, autouse=‘’, ids=‘’, name=‘’)

1)scope表示的是被@pytest.fiture标记的方法的作用域。function(默认),class,module,package/session

2)params:参数化(支持,列表[],元组(), 字典列表[{}, {}, {}],字典元组({}, {}, {}))

import pytest

@pytest.fixture(scope='function', params=['郭静', '周明', '王磊'])
def my_fixture(request)
	print('前置')
    yield
    print('后置')
    return request.param

class TestMashan:
    
    def test_01_baidu(self):
        print('测试百度')
        
    def test_02_huawei(self):
        print('测试华为')
        print('------------' + str(my_fixture))

params=[‘郭静’, ‘周明’, ‘王磊’] 这里 params 是参数名,有 s

request.para, 这里是属性名,没有 s

3)autouse=True:自动使用,默认False

4)ids:当使用 params 参数化时,给每一个设定一个变量名,意义不大

5)name:给表示的是被@pytest.fixture标记的方法取一个别名

当取了别名之后,那么原来的名称就用不了了

8.3 通过 conftest.py 和 @pytest.fixture() 结合使用全局的前置应用(比如:项目的全局登录,模块的全局处理)

1)conftest.py 文件是单独存放在一个夹具配置文件,名称是不能修改的

2)用处可以在不用的py文件中使用同一个 fixture 函数

3)原则上 conftest.py 需要和运行的用例放到同一层,并且不需要做任何的 import 导入操作

总结:

1)setup/teardown,setup_class/teardown_class 它是作用于所有的用例和所有的类

2)@pytest.fixture() 它的作用是既可以部分也可以全部前后置

3)conftest.py 和 @pytest .fixture() 结合使用,作用于全局的前后置

8.4 断言

assert

assert 1==2

8.5 pytest结合 allure-pytest 插件生成 allure 测试报告

1)下载

安装教程:https://www.cnblogs.com/XiqueBlogs/p/16178092.html

注:allure 使用前需要先安装 jdk

2)加入命令生成 json 格式的临时报告

--allure ./temp

3)生成 allure 报告

os.system('allure geneate ./temp -o ./report --clean')

需要在 pytest.ini 里面修改一下:

[pytest]

;addopts = -vs --html ./report/report.html
; 使用 allure 生成测试报告
addopts = -vs --alluredir ./temp
testpaths = ./testcase/test_baidu.py
python_files = test*.py
python_classes = Test*
python_functions = test
markers =
    smoke:冒烟用例
    usermanage:用户管理模块
    productmanage:商品管理模块

修改 all.py

# all.py

import os

import pytest

if __name__ == '__main__':
    pytest.main()
    os.system('allure generate ./temp -o ./report --clean')

执行 all.py 后,在 temp 目录下生成 json 格式的测试报告,在 report 目录下生成 html 格式的 allure 测试报告。

HTML格式测试报告:

PyTest_第15张图片

json格式测试报告:

PyTest_第16张图片


8.6 @pytest.mark.parametrize() 基本用法

@pytest.mark.parametrize(args_name, args_value)

args_name:参数名

args_value:参数值(列表, 元组, 字典列表,字典元组),有多个值用例就会执行多少次

用法一

假设我们现在在做接口测试,新建一个 testapi 的包,然后在里面存放接口测试用例:

在需要传参的用例上面加装饰器,把需要传的参数填写在上面,有几个参数,这个测试用例就跑几次:

PyTest_第17张图片

修改一下pytest.ini,让它只跑 testapi 包里面的测试用例

[pytest]

;addopts = -vs --html ./report/report.html
addopts = -vs
; 使用 allure 生成测试报告
;addopts = -vs --alluredir ./temp
testpaths = ./testapi
python_files = test*.py
python_classes = Test*
python_functions = test
markers =
    smoke:冒烟用例
    usermanage:用户管理模块
    productmanage:商品管理模块

在修改一下 all.py

import os

import pytest

if __name__ == '__main__':
    pytest.main()
    # os.system('allure generate ./temp -o ./report --clean')

执行结果,可以看到我们使用装饰器 @pytest.mark.parametrize('args', ['张三', '李四']) 传入了两个参数,所以测试用例 test_01_baidu() 执行了两次:

PyTest_第18张图片

用法二

假设传入的是多个变量,跟 unittest 的 ddt 里面的 @unpack 解包是一样的。

# test_api.py

import pytest

class TestApi:

    @pytest.mark.parametrize('name, age', [['张三', '18'],['周四', '45']])
    def test_01_baidu(self, name, age):
        print(name, age)

运行结果:

PyTest_第19张图片

8.7 YAML文件详解–实现接口自动化

1.用于全局的配置文件 ini/yaml

2.用于写测试用例(接口测试用例)

yaml简介:

yaml是一种数据格式,支持注释,换行,多行字符串,裸字符串(整型,字符串)

语法规则:

1.区分大小写

2.使用缩进表示层级,不能使用tab键进行缩进,只能使用空格

3.缩进是没有数量的,只要前面是对其的就行

4.注释是 #

数据组成

1.Map对象,键值对 键:(空格)值

jack:
  age: 18
  country: USA

2.数组对象,用一组横线开头,如:

三 YAML 接口自动化实战

anage:用户管理模块
productmanage:商品管理模块


在修改一下 all.py

```python
import os

import pytest

if __name__ == '__main__':
    pytest.main()
    # os.system('allure generate ./temp -o ./report --clean')

执行结果,可以看到我们使用装饰器 @pytest.mark.parametrize('args', ['张三', '李四']) 传入了两个参数,所以测试用例 test_01_baidu() 执行了两次:

[外链图片转存中…(img-liSdV6zK-1665244264356)]

用法二

假设传入的是多个变量,跟 unittest 的 ddt 里面的 @unpack 解包是一样的。

# test_api.py

import pytest

class TestApi:

    @pytest.mark.parametrize('name, age', [['张三', '18'],['周四', '45']])
    def test_01_baidu(self, name, age):
        print(name, age)

你可能感兴趣的:(pytest,pytest,单元测试)