IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 开发测试 -> 9.pytest 单元测试框架 -> 正文阅读

[开发测试]9.pytest 单元测试框架

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

1. pytest 简单例子

pytest 支持 pip 安装,pip install pytest

创建 test_sample.py 文件,代码如下:

def inc(x):
    return x + 1

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

命令行窗口,进入到 test_sample.py 文件所在目录,直接输入 “pytest” 即可。运行结果如下:
在这里插入图片描述
从上看出,pytest 的优点是它更加简单,不必创建测试类,使用 assert 断言也更加简单。

不过,pytest 也有自己的规则,即测试文件和测试函数必须以“test”开头。所以运行时才不用指定文件。

如果要像 unittest 通过 main() 方法执行测试用例,需要修改下 test_sample.py 文件:

import pytest

def inc(x):
    return x + 1

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

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

main()方法默认执行当前文件中所有以“test”开头的函数。这样就可以直接在 IDE 中运行测试了。

2. pytest 的基本使用方法

前面我们学习了 unittest 的基础,现在只需对比 pytest 与 unittest 之间的不同即可。

(1)断言

unittest 单元测试框架中提供了丰富的断言方法,而pytest 单元测试框架并没有提供专门的断言方法,而是直接使用 Python 的 assert 进行断言。

下面创建 test_assert.py 文件,介绍 pytest 断言的用法:(借助 Python 的运算符号和关键字即可轻松实现不同数据类型的断言。)

# 功能:用于计算 a 与 b 相加的和
def add(a, b):
    return a + b

# 功能:用于判断素数
def is_prime(n):
    if n <= 1:
        return False
    for i in range(2, n):
        if n % i == 0:
            return False
    return True

# 测试相等
def test_add1():
    assert add(3, 4) == 7
# 测试不相等
def test_add2():
    assert add(17, 22) != 50
# 测试大于或等于
def test_add3():
    assert add(17, 22) <= 50
# 测试小于或等于
def test_add4():
    assert add(17, 22) >= 38
# 测试包含
def test_in():
    a = 'hello'
    b = 'he'
    assert b in a
# 测试不包含
def test_not_in():
    a = 'hello'
    b = 'hi'
    assert b not in a
# 判断是否为 True
def test_true_1():
    assert is_prime(13)
# 判断是否为 True
def test_true_2():
    assert is_prime(7) is True
# 判断是否不为 True
def test_true_3():
    assert not is_prime(4)
# 判断是否不为 True
def test_true_4():
    assert is_prime(6) is not True
# 判断是否为 False
def test_false_1():
    assert is_prime(8) is False

(2)Fixture

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

创建 test_fixtures_01.py 文件:

# 功能函数
def multiply(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_multiply_3_4():
    print('test_numbers_3_4')
    assert multiply(3, 4) == 12

def test_multiply_a_3():
    print('test_strings_a_4')
    assert multiply('a', 3) == 'aaa'

然后命令行进入到该目录,执行命令 “pytest -s test_fixture_01.py”,运行结果如下:
“-s”参数用于关闭捕捉,从而输出打印信息。
在这里插入图片描述
这里主要用到模块级别和函数级别的 Fixture。

  • setup_module/teardown_module:在当前文件中,在所有测试用例执行之前与之后执行。
  • setup_function/teardown_function:在每个测试函数之前与之后执行。
  • setup/teardown:在每个测试函数之前与之后执行。(同样可以作用于类方法。)

pytest 是支持使用测试类的,同样必须以“Test”开头,注意首字母大写。
下面引入测试类,创建 test_fixtures_02.py 文件:

# 功能函数
def multiply(a, b):
    return a * b

class TestMultiply:
    # =====Fixture========
    @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_numbers_5_6(self):
        print('test_numbers_5_6')
        assert multiply(5, 6) == 30

    def test_strings_b_2(self):
        print('test_strings_b_2')
        assert multiply('b', 2) == 'bb'
    

在这里插入图片描述
这里主要用到类级别和方法级别的 Fixture。

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

(3)参数化

当一组测试用例有固定的测试数据时,就可以通过参数化的方式简化测试用例的编写。
pytest 本身是支持参数化的,不需要额外安装插件。

创建 test_parameterize.py 文件:

import pytest
import math

# pytest 参数化
@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,用于定义测试用例的名称。不设置dis值时运行结果如下:
在这里插入图片描述
math 模块的 pow()方法用于计算 xy(x 的 y 次方)的值。
命令行执行命令中,“-v”参数用于增加测试用例冗长。

(4)运行测试

pytest 提供了丰富的参数运行测试用例,前面我们知道,“-s”参数用于关闭捕捉,从而输出打印信息;“-v”参数用于增加测试用例冗长。
通过“pytest --help”可以查看帮助。

下面介绍常用的参数
a. 运行名称中包含某字符串的测试用例
在这里插入图片描述
test_assert.py 文件中有四个测试用例带字符串“add”,这里通过“-k”来指定在名称中包含“add”的4个测试用例。

**b.**减少测试的运行冗长
在这里插入图片描述
日志少了很多信息,“-q”用来减少测试运行的冗长;也可以使用“–quiet”代替。

**c.**如果出现一条测试用例失败,则退出测试
这在测试用例的调试阶段是有用的,有用例失败时应该先通过调试成功,再来执行后面测试用例。
创建 test_fail.py 文件:

def test_fail():
    assert (2 + 1) == 4

def test_success():
    assert (2 + 3) == 5

在这里插入图片描述
**d.**运行测试目录
在这里插入图片描述
测试目录既可以指定相对路径(如 ./test_dir ),也可以指定绝对路径(如D:\xxx\xxx…\test_dir)。

**e.**指定特定类或方法执行
在这里插入图片描述
这里指定运行 test_fixtures_02.py 文件中 TestMultiply 类下的 test_numbers_5_6()方法,文件名、类名和方法名之间用“::”符号分隔。

**f.**通过main()方法运行测试
创建 run_tests.py 文件,在文件中通过数组指定参数,每个参数为数组中的一个元素。(直接运行即可)

import pytest

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

在这里插入图片描述

(5)生成测试报告

pytest 支持生成多种格式的测试报告。

a. 生成 JUnit XML 文件

pytest ./test_dir --junit-xml=./report/log.xml
在这里插入图片描述
在这里插入图片描述
XML 类型的日志主要用于存放测试结果,方便我们利用里面的数据定制自己的测试报告。

b. 生成在线测试报告

pytest ./test_dir --pastebin=all
在这里插入图片描述
上述代码可生成一个 session-log 链接,复制链接,通过浏览器打开,会得到一张 HTML格式的测试报告(此处产生 HTTP 405 错误,暂未解决)

(6) conftest.py

conftest.py 是 pytest 特有的本地测试配置文件,既可以用来设置项目级别的 Fixture, 也可以用来导入外部插件,还可以用来指定钩子函数。

创建 test_project/conftest.py 测试配置文件:

import pytest

# 设置测试钩子
@pytest.fixture()
def test_url():
    return "http://www.baidu.com"

创建 test_project/test_web.py 测试用例文件:(这里的函数直接调用congtest.py 文件的 test_url()钩子函数)

def test_baidu(test_url):
    print(test_url)

在这里插入图片描述
需要说明的是,conftest.py 只作用于它所在的目录及子目录。

3.pytest 扩展

Pytest 可以扩展非常多的插件来实现各种功能,介绍几个对 Web 自动化测试非常有用的插件。

(1)pytest-html

pytest-html 可以生成 HTML 格式的测试报告。通过 pip 安装,pip install pytest-html。
pyest-html 还支持测试用例失败的截图(第四节)

pytest ./ --html=./report/result.html
在这里插入图片描述
在这里插入图片描述

(2)pytest-rerunfailures

pytest-rerunfailures 可以在测试用例失败时进行重试。通过 pip 安装,pip install pytest-rerunfailures

创建 test_rerunfailures.py

def test_fail_rerun():
    assert 2 + 2 == 5

pytest -v test_rerunfailures.py --reruns 3
通过“–reruns”参数设置测试用例运行失败后的重试次数。
在这里插入图片描述
因为 Web 自动化测试会因为网络等因素导致测试用例运行失败,而重试机制可以增加测试用例的稳定性。

(3)pytest-parallel 扩展

pytest-parallel 扩展可以实现测试用例的并行运行。使用 pip 安装,pip install pytest-parallel

创建 test_parallel.py,sleep() 模拟运行时间较长的测试用例:

from time import sleep

def test_01():
    sleep(3)

def test_02():
    sleep(5)

def test_03():
    sleep(6)

不使用线程运行测试用例:
在这里插入图片描述
使用线程运行测试用例:(参数“–tests-per-worker”用来指定线程数,“auto”表示自动分配)

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

该命令本地运行失败,报错:PermissionError: [WinError 5] 拒绝访问。
根据网上的解决方法,尝试修改 python 的权限(python.exe - 右键属性 - 安全 - 编辑 - Users(LAPTOP-ATGBMF1K\Users))- 完全控制√允许- 应用 - 确定 )
再重新执行上述命令,报错:OSError: [WinError 87] 参数错误。
网上的解决方法是,要么降低 python 的版本,要么降低 pytest 的版本试试看。(未尝试该方法)

pytest-parallel 的更多用法:

# runs 2 worker with 1 test per worker at a time
pytest --workers 2

# runs 4 workers (assuming a quad-core machine) with 1 test per worker
pytest --workers auto

# runs 1 worker with 4 tests at a time
pytest --tests-per-worker 4

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

# runs 2 workers with up to 50 tests per worker
pytest --workers 2 --tests-per-worker auto

并行运行测试可以非常有效地缩短测试的运行时间,但是 Web 自动化测试本身非常脆弱,在并行运行测试时很可能会产生相互干扰,从而导致测试用例失败,因此建议谨慎使用。

4.构建 Web 自动化测试项目

相比 unittest 单元测试框架,pytest 更适合用来做 UI 自动化测试,它提供以下功能:

  • 在 unittest 中,浏览器的启动或关闭只能基于测试方法或测试类;pytest 可以通过 conftest.py 文件配置全局浏览器的启动或关闭,整个自动化测试项目的运行只需启动或关闭一次浏览器即可,节省测试用例执行时间。
  • 测试用例运行失败截图。unittest 本身不支持该功能,pytest-html 可以实现测 试用例运行失败自动截图,只需在 conftest.py 中做相应的配置即可。
  • 测试用例运行失败重跑。UI 自动化测试的稳定性一直是难题,虽然可以通过元素等待来等待来增加稳定性,但有很多不可控的因素(如网络不稳定)导致测试用例运行失败,pytest-rerunfailures 可以轻松实现测试用例运行失败重跑。

(1)项目结构介绍

书本作者虫师放在GitHub上的 pyautoTest 项目是对基于 pytest 进行 UI 自动化测试实践的总结,在该项目的基础上,可以快速编写自己的自动化测试用例。

GitHub 地址:https://github.com/defnngj/pyautoTest。

a. pyautoTest 项目结构如图:
在这里插入图片描述

  • page/:用于存放 page 层的封装。
  • test_dir/:测试用例目录。
  • test_report/:测试报告目录。
  • conftest.py:pytest 配置文件。
  • run_tests.py:测试运行文件。

b. 命名与设计规范

  • 对于 page 层的封装存放于 page/目录,命名规范为“xxx_page.py”。
  • 对于测试用例的编写存放于 test_dir/目录,命名规范为“test_xxx.py”。
  • 每一个功能点对应一个测试类,并且以“Test”开头,如“TestLogin”“TestSearch”等。
  • 在一个测试类下编写功能点的所有的测试用例,如“test_login_user_null”、“test_login_pawd_null”及“test_login_success”等。

c. 克隆与安装依赖

  • 安装 Git 版本控制工具,将 pyautoTest 项目克隆到本地。(或者直接访问上面GitHub地址,下载zip格式文件到本地)
  • 通过 pip 命令安装依赖。pip install -r requirements.txt (执行命令前,命令行需要进入到该文件所在项目)

d. 依赖库说明

  • selenium:Web UI 自动化测试。
  • pytest:Python 第三方单元测试框架。
  • pytest-html:pytest 扩展,生成 HTML 格式的测试报告。
  • pytest-rerunfailures:pytest 扩展,实现测试用例运行失败重跑。
  • click:命令行工具开发库。
  • poium:基于 Selenium/appium 的 Page Object 测试库。

(2)主要代码实现

封装页面 Page 层,创建 page/baidu_page.py 文件。

from poium import Page, Element

class BaiduPage(Page):
    search_input = Element(id_="kw", describe="搜索框")
    search_button = Element(id_="su", describe="搜索按钮")
    settings = Element(css="#s-usersetting-top", describe="设置")
    search_setting = Element(css="#s-user-setting-menu > div > a.setpref", describe="搜索设置")
    save_setting = Element(link_text="保存设置", describe="保存设置")

编写测试用例,创建 test_dir/test_baidu.py 文件。

import sys
from time import sleep
import pytest
from os.path import dirname, abspath

sys.path.insert(0, dirname(dirname(abspath(__file__))))
from page.baidu_page import BaiduPage

class TestSearch:
    """百度搜索"""

    def test_baidu_search_case(self, browser, base_url):
        page = BaiduPage(browser)
        page.get(base_url)
        page.search_input = "pytest"
        page.search_button.click()
        sleep(2)
        assert browser.title == "pytest_百度搜索"
        
# ....

test_baidu_search_case()函数,接收 conftest.py 文件中定义的 browser 和 base_url 钩子函数。

在测试用例中,可以将注意力集中在测试用例设计本身的操作上,而不需要关心浏览器驱动、访问的 URL 以及测试用例运行失败截图,这些都在 conftest.py 文件中配置好。

a. conftest.py 文件之自动配置(这里用 config.py 文件来放这些自动配置,然后再在conftest.py 文件中调用)

import os
PRO_PATH = os.path.dirname(os.path.abspath(__file__))

class RunConfig:
    """
    运行测试配置
    """
    # 运行测试用例的目录或文件
    cases_path = os.path.join(PRO_PATH, "test_dir", "test_baidu.py")
    # 配置浏览器驱动类型(chrome/firefox/chrome-headless/firefox-headless)。
    driver_type = "chrome"
    # 配置运行的 URL
    url = "https://www.baidu.com"
    # 失败重跑次数
    rerun = "1"
    # 当达到最大失败数,停止执行
    max_fail = "5"
    # 浏览器驱动(不需要修改)
    driver = None
    # 报告路径(不需要修改)
    NEW_REPORT = None

b. conftest.py 文件之浏览器配置

config.py 文件中配置了浏览器类型:driver_type = “chrome”

# 启动浏览器
@pytest.fixture(scope='session', autouse=True)
def browser():
    """
    全局定义浏览器驱动
    :return:
    """
    global driver

    if RunConfig.driver_type == "chrome":
        # 本地chrome浏览器
        driver = webdriver.Chrome()
        driver.maximize_window()

    elif RunConfig.driver_type == "firefox":
        # 本地firefox浏览器
        driver = webdriver.Firefox()
        driver.maximize_window()

    elif RunConfig.driver_type == "chrome-headless":
        # chrome headless模式
        chrome_options = CH_Options()
        chrome_options.add_argument("--headless")
        chrome_options.add_argument('--disable-gpu')
        # chrome_options.add_argument("--window-size=1920x1080")
        driver = webdriver.Chrome(options=chrome_options)

    elif RunConfig.driver_type == "firefox-headless":
        # firefox headless模式
        firefox_options = FF_Options()
        firefox_options.headless = True
        driver = webdriver.Firefox(firefox_options=firefox_options)

    elif RunConfig.driver_type == "grid":
        # 通过远程节点运行
        driver = Remote(command_executor='http://localhost:4444/wd/hub',
                        desired_capabilities={
                              "browserName": "chrome",
                        })
        driver.set_window_size(1920, 1080)

    else:
        raise NameError("driver驱动类型定义错误!")

    RunConfig.driver = driver

    return driver


# 关闭浏览器
@pytest.fixture(scope="session", autouse=True)
def browser_close():
    yield driver
    driver.quit()
    print("test end!")

Selenium 在启动浏览器时会创建一个 session,当通过@pytest.fixture()装饰浏览器开启和关闭函数时,scope 参数需要设置为“session”。browser()函数用于定义浏览器,根据变量 driver_type 的定义创建不同的浏览器驱动。

c. conftest.py 文件之失败截图配置

...
@pytest.mark.hookwrapper
def pytest_runtest_makereport(item):
    """
    用于向测试用例中添加用例的开始时间、内部注释,和失败截图等.
    :param item:
    """

    pytest_html = item.config.pluginmanager.getplugin('html')
    outcome = yield
    report = outcome.get_result()
    report.description = description_html(item.function.__doc__)
    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):
            case_path = report.nodeid.replace("::", "_") + ".png"
            if "[" in case_path:
                case_name = case_path.split("-")[0] + "].png"
            else:
                case_name = case_path
            capture_screenshots(case_name)
            img_path = "image/" + case_name.split("/")[-1]
            if img_path:
                html = '<div><img src="%s" alt="screenshot" style="width:304px;height:228px;" ' \
                       'οnclick="window.open(this.src)" align="right"/></div>' % img_path
                extra.append(pytest_html.extras.html(html))
        report.extra = extra

def capture_screenshots(case_name):
    """
    配置用例失败截图路径
    :param case_name: 用例名
    :return:
    """
    global driver
    file_name = case_name.split("/")[-1]
    if RunConfig.NEW_REPORT is None:
        raise NameError('没有初始化测试报告目录')
    else:
        image_dir = os.path.join(RunConfig.NEW_REPORT, "image", file_name)
        RunConfig.driver.save_screenshot(image_dir)

...

核心参考 pytest-html 文档。
pytest_runtest_makereport()钩子函数的主要功能是判断每条测试用例的运行情况,当测试用例错误或失败后会调用
capture_screenshot()函数进行截图,并将测试用例的“文件名+类名+方法名”作为截图的名称,保存于 image/目录中。

pytest-html 会生成一张 HTML 格式的测试报告,将截图插入 HTML 格式测试报告的核心就是添加标签,并通过 src 属性指定图片的路径。

(3)测试用例的运行与测试报告

项目中的关键文件 run_tests.py,它用来执行整个项目的测试用例。

...
@click.command()
@click.option('-m', default=None, help='输入运行模式:run 或 debug.')
def run(m):
    if m is None or m == "run":
        logger.info("回归模式,开始执行??!")
        now_time = time.strftime("%Y_%m_%d_%H_%M_%S")
        RunConfig.NEW_REPORT = os.path.join(REPORT_DIR, now_time)
        init_env(RunConfig.NEW_REPORT)
        html_report = os.path.join(RunConfig.NEW_REPORT, "report.html")
        xml_report = os.path.join(RunConfig.NEW_REPORT, "junit-xml.xml")
        pytest.main(["-s", "-v", RunConfig.cases_path,
                     "--html=" + html_report,
                     "--junit-xml=" + xml_report,
                     "--self-contained-html",
                     "--maxfail", RunConfig.max_fail,
                     "--reruns", RunConfig.rerun])
        logger.info("运行结束,生成测试报告??!")
    elif m == "debug":
        print("debug模式,开始执行!")
        pytest.main(["-v", "-s", RunConfig.cases_path])
        print("运行结束!!")

if __name__ == '__main__':
    run()

上述代码使用了命令行工具开发库 click。click 提供了两种运行模式:run 模式和 debug 模式。不同模式下的 pytest 的执行参数不同。
在这里插入图片描述
运行命令: python run_tests.py -m run
在这里插入图片描述
生成的 HTML 测试报告在 test_report 目录下,当测试用例运行失败时自动截图,并显示在 HTML 测试报告中。

  开发测试 最新文章
pytest系列——allure之生成测试报告(Wind
某大厂软件测试岗一面笔试题+二面问答题面试
iperf 学习笔记
关于Python中使用selenium八大定位方法
【软件测试】为什么提升不了?8年测试总结再
软件测试复习
PHP笔记-Smarty模板引擎的使用
C++Test使用入门
【Java】单元测试
Net core 3.x 获取客户端地址
上一篇文章      下一篇文章      查看所有文章
加:2021-07-25 11:58:14  更:2021-07-25 11:58:37 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年4日历 -2024/4/20 3:15:53-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码