pytest基本应用

1. 简介:

pytest是python的一种单元测试框架,比Python自带的Unittest框架使用起来更简洁,效率更高。根据pytest的官方网站介绍(官方文档:https://docs.pytest.org/en/latest/contents.html),它具有如下特点:

  • 非常容易上手,入门简单,文档丰富,文档中有很多实例可以参考
  • 能够支持简单的单元测试和复杂的功能测试
  • 支持参数化
  • 执行测试过程中可以将某些测试跳过(skip),或者对某些预期失败的case标记成失败
  • 支持重复执行(rerun)失败的case
  • 支持运行由nose, unittest编写的测试case
  • 可生成html报告
  • 方便的和持续集成工具jenkins集成
  • 可支持执行部分用例
  • 具有很多第三方插件,并且可以自定义扩展

2. 安装:

# 一般情况都是模式安装最新版的,对于特殊有版本依赖的可以使用package==版本号来指定版本

pip install pytest -i https://pypi.tuna.tsinghua.edu.cn/simple --trusted-host pypi.tuna.tsinghua.edu.cn

# 查看安装package的版本号的命令很多,如下任意一个都可以
# 版本号:6.1.2
# 在cmd 命令行
pip list 
pip show pytest
pytest --version
# 在python交互命令
import pytest
print(pytest.__version__)

3. 使用示例:

a. 测试函数:每个函数是一个测试用例,简单方便

# 待测试函数
def add(x, y):
    return x+y

# test01  function 
def test_add_01():
    assert add(2, 8) == 10

# test02 funciton
def test_add_02():
    assert add(3, 7) == 9
# 运行方式
# 方式1: 在测试case所在的文件下打开terminal 输入:pytest或者py.test
# 方式2:在pycharm中的输入
if __name__ == '__main__':
	pytest.main(['-q', 'module.py'])  # 当前文件的名称
# 方式3:按节点执行,pytest test_mod.py::TestClass::test_method	

b. 测试类:把多个测试用例,写到一个测试类里

# file: test_class.py

class TestClass():

    def test_class_1(self):
        x = 1
        assert 1 == 1

    def test_class_2(self):
        x = 3
        assert 3 == "3"
# 运行方式
# 1. 进入文件所在的终端terminal
# 2. 输入:py.test -q test_class.py

4.运行规则:

a) 用例查找规则

  1. 查找当前目录及其子目录下以test_.py或_test.py文件,
  2. 找到文件后,在文件中找到以test开头函数并执行
  3. 可以指定运行指定测试文件、测试方法及测试函数
  4. unittest实现该方式需要自己写方法来实现, os.walk()

b) 用例运行方式:

# 1.查看pytest命令行参数,可以用pytest -h 或pytest --help
# 2.cmd 运行
# 方式1 
pytest
# 方式2
py.test
# 方式3
python -m test_file.py

# 运行.py模块里面的某个函数
pytest test_mod.py::test_func

# 运行.py模块里面,测试类里面的某个方法
pytest test_mod.py::TestClass::test_method

# 3. pycharm运行
# pycharm设置:以pytest方式运行,需要改该工程设置默认的运行器:file->Setting->Tools->Python Integrated Tools->项目名称->Default test runner->选择py.test

if __name__ == "__main__":
    pytest.main(['-q', 'test_pytest.py::test_add_01'])
# 常用参数:
# -x 遇到错误终止运行
# --maxfail=num  遇到num个错误后终止运行
# -q --quiet decrease verbosity 参数只显示结果,不显示过程
# -s 参数是为了显示用例的打印信息

5.用例规则:

  1. 测试文件以test_开头(以_test结尾也可以)
  2. 测试类以Test开头,并且不能带有 __init__方法
  3. 测试函数以test_开头
  4. 断言使用assert
  5. 所有的包package必须要有 __init__文件

6.测试用例的预置条件

setup和teardown可以实现在测试用例之前或之后加入一些操作,这种设置是整个脚本全局生效的,用法类似unittest的,但是比uinttest强大。

  1. 模块级(setup_module/teardown_module)开始于模块始末,全局的
  2. 函数级(setup_function/teardown_function)只对函数用例生效 --不在类中
  3. 类级(setup_class/teardown_class)只在类中前后运行一次 --在类中
  4. 方法级(setup_method/teardown_method)开始于方法始末 --在类中
  5. 类里面的(setup/teardown)运行在调用方法的前后 --在类中 与4的作用一样
import pytest

def setup_module():
    print("每个模块开始前执行一次")


def teardown_module():
    print("每个模块结束后执行一次")


def setup_function():
    print("******方法前开始前执行*******")


def teardown_function():
    print("*****方法结束后执行******")


def add(x, y):
    return x + y


def test_add_01():
    print("***01***")
    assert add(3, 4) == 7


def test_add_02():
    print("***02***")
    assert add(3, 5) == 8

class TestClass():
    def setup(self):
        print("setup: 每个用例开始前执行")

    def teardown(self):
        print("teardown: 每个用例结束后执行")

    def setup_class(self):
        print("setup_class:所有用例执行之前")

    def teardown_class(self):
        print("teardown_class:所有用例执行之前")

    def setup_method(self):
        print("setup_method:  每个用例开始前执行")

    def teardown_method(self):
        print("teardown_method:  每个用例结束后执行")

    def test_class_1(self):
        print("*****01******")
        x = 1
        assert 1 == 1

    def test_class_2(self):
        print("*****01******")
        x = 3
        assert 3 == "3"


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

7.自定义测试用例的预置条件

fixture

  • firture相对于setup和teardown来说应该有以下几点优势
  • 命名方式灵活,不局限于setup和teardown这几个命名
  • scope=“module” 可以实现多个.py跨文件共享前置, 每一个.py文件调用一次
  • scope=“session” 以实现多个.py跨文件使用一个session来完成多个用例
  • scope=“function” 默认值,针对函数有效,同setup_function
  • scope=“class” 每个类开始前后执行一次,同setup_class

conftest.py

  • 上面一个案例是在同一个.py文件中,多个用例调用一个登陆功能,如果有多个.py的文件都需要调用这个登陆功能的话,那就不能把登陆写到用例里面去了。此时应该要有一个配置文件,单独管理一些预置的操作场景,pytest里面默认读取conftest.py里面的配置
  • conftest.py配置脚本名称是固定的,不能改名称
  • conftest.py与运行的用例要在同一个pakage下,并且有__init__.py文件
  • 不需要import导入 conftest.py,pytest用例会自动查找

yield

  • fixture的teardown操作并不是独立的函数,用yield关键字呼唤teardow
  • 如果其中一个用例出现异常,不影响yield后面的teardown执行,运行结果互不影响,并且全部用例执行完之后,yield呼唤teardown操作
  • 如果在setup就异常了,那么是不会去执行yield后面的teardown内容了
  • yield也可以配合with语句使用,可参见官方文档
  • 除了yield可以实现teardown,在request-context对象中注册addfinalizer方法也可以实现终结函数
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @File  : test_fixture.py
# @Author: Nan Luo
# @Date  : 2021/6/15 23:55
# @Desc  :

import pytest

# 同一目录下的新增conftest.py文件
@pytest.fixture()
def login():
   print("****登录系统****")
   
   yield
   print("执行teardown操作")
   print("*****登出系统******")


def test_01(login):
    print("登录后,执行语句01")
    assert 1 == 1


def test_02():
    print("不需要登录,执行语句2")
    assert 2 == 2


def test_03(login):
    print("登录后,执行语句3")


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

常用断言

pytest里面断言实际上就是python里面的assert断言方法,常用的有以下几种

  1. assert xx 判断xx为真
  2. assert not xx 判断xx不为真
  3. assert a in b 判断b包含a
  4. assert a== b 判断a等于b
  5. assert a != b 判断a不等于b

8.测试报告

a. pytest-HTML

  • 源码地址:https://github.com/pytest-dev/pytest-html
  • 安装方式:pip install pytest-html
  • 使用方法:pytest --html==report.html
  • 常用方式:pytest --html ==./report/report.html --self-contained-html # 在指定路径生成html报告,并且把样式直接添加在html中,避免在分享报告时丢失样式
  • 更多功能:https://github.com/pytest-dev/pytest-html
  • 添加截图:实现参考代码
  • 失败重跑:pip install pytest-rerunfailures
  • 使用方式:pytest --reruns 1 --html ==./report/report.html --self-contained-html
# conftest.py
import pytest
from selenium import webdriver
driver = None

@pytest.fixture()
def login():
    print("****登录系统****")

    yield
    print("执行teardown操作")
    print("*****登出系统******")


@pytest.mark.hookwrapper
def pytest_runtest_makereport(item):
    """
    当测试失败的时候,自动截图,展示到html报告中
    :param item:
    """
    pytest_html = item.config.pluginmanager.getplugin('html')
    outcome = yield
    report = outcome.get_result()
    extra = getattr(report, 'extra', [])

    if report.when == 'call' or report.when == "setup":
        xfail = hasattr(report, 'wasxfail')
        if (report.skipped and xfail) or (report.failed and not xfail):
            file_name = report.nodeid.replace("::", "_")+".png"
            screen_img = _capture_screenshot()
            if file_name:
                html = '<div><img src="https://img-blog.csdnimg.cn/2022010616204883800.png" alt="screenshot" style="width:600px;height:300px;" ' \
                       'οnclick="window.open(this.src)" align="right"/></div>' % screen_img
                extra.append(pytest_html.extras.html(html))
        report.extra = extra


def _capture_screenshot():
    '''
    截图保存为base64,展示到html中
    :return:
    '''
    return driver.get_screenshot_as_base64()


@pytest.fixture(scope='session', autouse=True)
def browser(request):
    global driver
    if driver is None:
        driver = webdriver.Chrome()

    def end():
        driver.quit()
    request.addfinalizer(end)
    return driver

# test_01.py
from selenium import webdriver
import time

def test_captrue_01(browser):

    browser.get("https://www.baidu.com/")
    time.sleep(2)
    t = browser.title
    assert t == "百度"

# 运行:cmd
# pytest --html=./report/report.html --self-contained-html
# 当前实现已知BUG, 只要是失败就截图,无法精确关联

b. 安装allure-pytest模块

# 安装allure-pytest
pip install allure-pytest -i https://pypi.tuna.tsinghua.edu.cn/simple --trusted-host pypi.tuna.tsinghua.edu.cn
  1. 安装allure命令行工具,下载地址:
  2. 设置环境变量:把命令行bin目录的路径添加到环境变量中
  3. 运行用例:cmd 到case目录下执行,pytest --alluredir ./report/allure_raw
  4. 启动allure服务:allure serve report/allure_raw --在命令行中执行 虚拟环境无法识别
  • 参考文档:https://blog.csdn.net/lovedingd/article/details/98952868
  • 学习中常见错误处理:
    1、https://blog.csdn.net/u013238020/article/details/105905343
    –实际处理方式:pip list | grep allure pip uninstall allure

9. 参数化parametrize

pytest.mark.parametrize装饰器可以实现测试用例参数化。

import pytest

# 实现检查一定的输入和期望输出测试功能的典型例子
# 可以标记单个测试实例在参数化,例如使用内置的mark.xfail
@pytest.mark.parametrize("test_input,expected",
                         [("3+5", 8),
                          ("2+4", 6),
                          ("6 * 9", 42),
                          pytest.param("6 * 8", 42, marks=pytest.mark.xfail),
                         ])
def test_eval(test_input, expected):
    assert eval(test_input) == expected


# 参数组合
# 参数设置为x=0/y=2,x=1/y=2,x=0/y=3,x=1/y=3
# 可以用于接口测试的场景组合测试
@pytest.mark.parametrize("x", [0, 1])
@pytest.mark.parametrize("y", [2, 3])
def test_foo(x, y):
    print("测试数据组合:x->%s, y->%s" % (x, y))


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

10. pytest.ini 配置文件

放置位置: 工程的根目录下

[pytest]
xfail_strict = true
addopts = -v --reruns 1 --html=report.html --self-contained-html
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值