软件测试 Pytest

158 阅读7分钟

该文章为测试学习笔记

  • 自动化测试:提前准备好数据,测试完成后,自动清理脏数据,有没有更好的框架?
  • 自动化测试,需要使用多套测试数据实现用例的参数化,有没有更便捷的方式?
  • 自动化测试,需要自动生成优雅、简洁的测试报告,有没有更好的生成方法?

Pytest

  • 支持简单的单元测试和复杂的功能测试;
  • 结合Requests实现接口测试;结合Selenium、Appium实现自动化功能测试;
  • 使用pytest结合Allure集成到Jenkins中可以实现持续集成;
  • 支持315种以上插件;
  1. 兼容unittest
  2. 定制化插件开发

一个简单的test :用assert来确定是否符合

def inc(x):
    return x + 1


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

Pytest环境安装

  1. 前提:Python环境 > 3.6 有对应pip
  2. 第一种方式 pip install pytest
  3. 第二种方式 PyCharm直接安装

可能出错的地方

  1. 找不到pip python路径有错误或没有存入 +"\Scripts"的地址

运行第一个脚本

  1. 进入py文件存储的文件夹下
  2. 输入pytest [名字]
pytest demo1.py
  • 错误情况下

image.png

  • 正确情况下

image.png

Pytest格式要求

  • 文件名
  • 类名
  • 方法/函数名(类内用def定义的为方法,在类外的为函数)
类型规则
文件test_开头或者_test结尾
Test开头
方法/函数test_开头
无要求

注意:测试类中不可以_init_构造函数, 添加该函数后系统就不认为其为测试类了,不会运行方法了

Pycharm 配置与界面化运行

Pycharm默认测试执行器为Pytest

  1. 创建一个项目 同时选择一个虚拟环境

image.png

  1. 点击图示加号
  2. 搜索pytest,下载(左下角)

image.png

  1. 重新进入Setting界面(前几步下载好的pytest才会被识别到,避免报错),进入Tools->Python Intergrated Tools

image.png 5. 选择Default test runner 为pytest image.png

用例结构

  • 用例名称
  • 用例步骤
  • 用例断言

示例

class TestXXX:
    def setup(self):
        # 资源准备
        pass
    def teardown(self):
        # 资源销毁 关闭页面,关闭数据库连接等等
        pass
    def test_XXX(self):
        # 测试步骤1
        # 测试步骤2
        # 断言 实际结果 对比 预期结果
        assert ActualResult == ExpectedResult

断言Assert

通过 or AssertionError异常

写法

image.png

Pytest测试框架结构(setup/teardown)

类型规则
setup_module/teardown_module全局模块级
setup_class/teardown_class类级,只在类中前后运行一次
setup_function/teardown_function函数级,在类外
setup_method/teardown_method方法级,类中的每个方法执行前后
setup/teardown在类中,运行在调用方法的前后

运行某个模块

# 模块级别 只被调用一次
def setup_module():
    print("资源准备:setup module")


def teardown_module():
    print("资源准备:teardown module")


def test_case1():
    print("case1")


def test_case2():
    print("case2")


def setup_function():
    print("资源准备:setup function")


def teardown_function():
    print("资源准备:teardown function")

下图中左上角的两个按钮需要勾选,才能显示完整信息 image.png

控制台信息显示如下

资源准备:setup module
资源准备:setup function
PASSED                                         [ 50%]case1
资源准备:teardown function

test_first.py::test_case2 资源准备:setup function
PASSED                                         [100%]case2
资源准备:teardown function
资源准备:teardown module


============================== 2 passed in 0.02s ==============================

Process finished with exit code 0

运行单个方法

加入以下代码, 点击 三角 运行

class TestDemo:
    # 执行类 前后分别执行setup_class teardown_class
    def setup_class(self):
        print("TestDemo setup_class")

    def teardown_class(self):
        print("TestDemo teardown_class")

    # 每个类里面的方法前后分别执行setup teardown
    def setup(self):
        print("TestDemo setup")

    def teardown(self):
        print("TestDemo teardown")

    def test_demo1(self):
        print("test demo1")

    def test_demo2(self):
        print("test demo2")

image.png

参数化

参数化设计方法是将模型中的定量信息 变量化,使之成为任意调整的参数。对于变量化参数赋予不同数值,可得到不同大小和形状的零件模型。

Mark:参数化测试函数使用

  • 单参数
  • 多参数
  • 用例重命名
  • 笛卡尔积

image.png

最底层的对勾数量为用例个数

eval的使用方法

image.png

出错时:

image.png

  1. 例子1
import pytest

search_list = ['appium', 'selenium', 'pytest']

@pytest.mark.parametrize('name', search_list)
def test_search(name):
    assert name in search_list

2. 例子2

import pytest


@pytest.mark.parametrize("test_input, expected", [
    ("3+5", 8), ("2+5", 7), ("7+5", 12)
])
def test_search(test_input, expected):
    assert eval(test_input) == expected

3. ids的作用

import pytest

# ids的个数 == 传递的数据个数
@pytest.mark.parametrize("test_input, expected", [
    ("3+5", 8), ("2+5", 7), ("7+5", 12)
], ids=["number1", "number2", "number3"])
def test_search(test_input, expected):
    assert eval(test_input) == expected

image.png

  1. 笛卡尔积
import pytest


# ids的个数 == 传递的数据个数
@pytest.mark.parametrize("test_a", ["a1", "a2", "a3"])
@pytest.mark.parametrize("test_b", ["b1", "b2", "b3", "b4"])
def test_search(test_a, test_b):
    print(f"test_a {test_a}, test_b {test_b}")
# PASSED                                 [  8%]test_a a1, test_b b1
# PASSED                                 [ 16%]test_a a2, test_b b1
# PASSED                                 [ 25%]test_a a3, test_b b1
# PASSED                                 [ 33%]test_a a1, test_b b2
# PASSED                                 [ 41%]test_a a2, test_b b2
# PASSED                                 [ 50%]test_a a3, test_b b2
# PASSED                                 [ 58%]test_a a1, test_b b3
# PASSED                                 [ 66%]test_a a2, test_b b3
# PASSED                                 [ 75%]test_a a3, test_b b3
# PASSED                                 [ 83%]test_a a1, test_b b4
# PASSED                                 [ 91%]test_a a2, test_b b4
# PASSED                                 [100%]test_a a3, test_b b4

内部逻辑代码:点击.parametrize查看

Pytest 标记测试用例

import pytest


def double(a):
    return a * 2


# 测试数据:整型
@pytest.mark.int
def test_double_int():
    print("test double int")
    assert 2 == double(1)


# 测试数据:负数
@pytest.mark.minus
def test_double_minus():
    print("test double minus")
    assert -2 == double(-1)


# 测试数据:浮点数
# 名字相同时,使两个两个同时进行
@pytest.mark.float
def test_double_float():
    assert 0.2 == double(0.1)


@pytest.mark.float
def test_double2_minus():
    assert -0.2 == double(-0.1)


@pytest.mark.zero
def test_double_0():
    assert 0 == double(0)


@pytest.mark.bignum
def test_double_bignum():
    assert 200 == double(100)


@pytest.mark.str
def test_double_str():
    assert 'aa' == double('a')


def test_double_str1():
    assert 'a$a$' == double('a$')

1.

image.png

  1. 输入
# pytest 文件名字.后缀 -vs -m  "某一个标签,例如str add等 需要双引号括住"
pytest test_first.py -vs -m  "str"

3. 消除warning

image.png

  1. 查看warning,发现还有一个警告,再查看上方的文字,发现是注释的zero未添加上去

  2. 警告减少了,再次运行 显示总共8个数据,7个未被选中,1个被选中

image.png

Mark:跳过(Skip)及预期失败(xFail)

pytest内置标签,可以处理一些特殊or不能成功运行(某功能未实现,先不检测)的测试用例

Skip

共两种解决方案

添加装饰器

  • @pytest.mark.skip
import pytest


@pytest.mark.skip
def test_aaa():
    print("start")
    assert True


def test_bbb():
    print("end")
    assert True

image.png

image.png 执行结果如上图所示

添加reson后:

import pytest


@pytest.mark.skip(reason="存在bug")
def test_aaa():
    print("start")
    assert True


def test_bbb():
    print("end")
    assert True

image.png

  • @pytest.mark.skipif

因为运算的电脑是win32,所以第二个用例不执行

import pytest
import sys

print(sys.platform)


@pytest.mark.skipif(sys.platform == 'darwin', reason="does not run on mac")
def test_case1():
    assert True


@pytest.mark.skipif(sys.platform.__contains__('win') , reason="does not run on windows")
def test_case2():
    assert True


@pytest.mark.skipif(sys.version_info < (3, 6), reason='requires python3.6 or higher')
def test_case3():
    assert True

代码中添加跳过代码

import pytest


def login():
    return True


def test_login():
    print("start")
    # 符合,则跳过下列步骤
    if not login():
        pytest.skip("no login")

    print("end")

点击左侧三角,执行,如下图

image.png

控制台输出如下图

image.png

  1. 将方法中Ture改为False,执行
import pytest


def login():
    return False


def test_login():
    print("start")
    # 符合,则跳过下列步骤
    if not login():
        pytest.skip("no login")

    print("end")

image.png 后续不执行

xfail

使用场景:预期结果为fail

@pytest.mark.xfail

import pytest


@pytest.mark.xfail
def test_case1():
    assert 1 == 2


xfail = pytest.mark.xfail


@xfail(reason="bug 110")
def test_case2():
    assert 1 == 1

运行用例

  • 方法
  • 多个类 多选即可
  • 模块

两种方法:

  1. 可视化界面操作
  2. 命令
pytest # ls下所有的可执行文件都执行
pytest test_first.py # 执行某一个文件
pytest test_first.py::test_case1 # 执行某个类或者方法pytest test_first.py::test_case1 -v # 查看具体哪一条被执行了
pytest test_first::class::test_case1 # 执行某个类中的某个用例
pytest -v # 执行当前目录下的所有目录

运行结果

  1. 常见:fail失败、error代码不符合规范,需要修改后才能执行、pass通过,一致
  2. 特殊:
    • warning警告,提示作用
    • deselect未被选中