在 PyCharm 里使用 Pytest 测试框架

在上一小节已经介绍了基于 Python unittest 在 PyCharm 里执行测试相关知识,本节将继续介绍另一个比较流行的测试框架 Pytest。

1. Pytest 介绍

Pytest 是 Python 语言中一款强大的单元测试框架,它是基于 unittest 开发的扩展框架,用来管理和组织测试用例,可应用在单元测试、自动化测试工作中。

Pytest 主要特点:

1. 兼容性好: 支持 Python 2.7,Python 3.4+。

2. 与 unittest 和 nose 测试框架兼容: 如果之前测试用例全部是基于 unittest 或者 nose 来编写的,执行 Pytest 命令同样可以正常运行并得到结果。因此无需担心迁移测框架从而带来额外的人工成本。

3. 丰富的插件支持: 大约有 300 多个,像 Pytest-repeat, pytest-xdist,pytest-ordering,pytest-rerunfailures 以及 pytest-html 这些常用插件在测试重复执行、并发与生成报告方面都提供了非常强大的支持。

4. 允许直接使用 assert 进行断言:相比 unittest 简单,unittest 定义了 assertEqual、assertIn、assertTrue、assertFalse 等一系列断言。

5. 可以自动寻找单测文件、类和函数:Pytest 要求所有的单测文件名都需要满足 test_.py 格式或_test.py 格式。在单测文件中,可以包含 test_ 开头的函数,也可以包含 Test 开头的类。在单测类中,可以包含一个或多个 test_ 开头的函数。在执行 Pytest 命令时,会自动从当前目录及子目录中寻找符合上述约束的测试函数来执行。

6. 提供应用不同范围前置/后置方法: Pytest 提供了模块级、函数级、类级、方法级的setup/teardown,比 unittest 的 setUp/tearDown 更灵活。

7. 分类执行与测试数据参数化简单 : unittest 需依赖 ddt 库实现数据参数化,而 Pytest 直接使用@pytest.mark.parametrize 装饰器。

总之, Pytest 因为其功能强大性及使用的方便性,已经成为主流的测试框架之一,unittest 更适合入门级的测试,当执行比较复杂的测试 Pytest 就更适合一些,这也是为什么我们会单列一小节讲 Pytest 的原因。更多关于 Pytest 使用请参考官网

2. 使用 Pytest 执行测试

在 PyCharm 里使用 Pytest 执行测试的过程与 unittest 是一致的,所以对于步骤上的操作在本小节将不再过多说明,将把重点关注在 Pytest 一些特性上。

2.1 配置测试框架

主菜单: PyCharm/File -> Preference/Settings -> Tools -> Python Integrated Tools, 点击 Default Test Runner 选择 Pytest。
图片描述

2.2 创建测试

创建一个新的 Python project,增加新文件 rectangle.py,添加下面的代码到文件, 继续沿用上一小节用到的待测代码。

import math


class Rectangle:
    def __init__(self, length, width, size=(40, 20)):
        self.length = length
        self.width = width
        self._size = size

    def area(self):
        area = self.length * self.width
        return area

    def perimeter(self):

        perimeter = (self.length + self.width) * 2
        return perimeter

    def diff(self):

        diff = math.fabs(self.length - self.width)
        return diff
    
    def resize(self, width, height):
        if width <= 0 or height <= 0:
            raise ValueError("illegal size")
        self._size = (width, height)

    def get_length(self):
        return self.length

    def get_width(self):
        return self. width

在编辑器中,将光标放在类声明或方法中的位置。

  • 从主菜单中,选择 Navigate -> Test
  • 编辑器内,右键上下文菜单中选择 Go to -> Test (⌘⇧T: Ctrl + Shift + T)

PyCharm 显示可用测试的列表。单击"创建新测试"。在打开 Create test 对话框中进行设置, 点击 OK 会自动生成测试文件 test_rectangle 与 测试方法模板。

图片描述

生成的模板 如下图所示: 没有像 unittest 那样创建同名测试类,无需像导入 unittest 一样导入pytest , 断言直接用 assert。使用pytest 写测试用例看上去更简单一些。

图片描述

2.3 修改代码与执行测试

根据 Pytest 规则更新代码:

from rectangle import Rectangle


def test_area():
    rect = Rectangle(30, 15)
    assert rect.area() == 450


def test_perimeter():
    rect = Rectangle(30, 15)
    assert rect.perimeter() == 90

在 编辑器的上下文菜单,选择 Run pytest for Name,会默认运行当前文件所有以 ‘test’ 开头的所有方法。Run 窗口会自动弹出,显示测试结果。

图片描述

3. Pytest 一些特性

从上面介绍可以看出,无论用哪种测试框架,基本流程都是一样的。下面介绍一些Pytest 一些特性,这些特性都是使用频率比较高,也是相对于unittest 测试功能更为便利与先进的功能。

3.1 使用 fixture

fixture 是 Pytest 特有的功能,它用 pytest.fixture 标识,定义在函数前面。在你编写测试函数的时候,你可以将此函数名称做为传入参数,pytest将会以依赖注入方式,将该函数的返回值作为测试函数的传入参数。

我们可以把fixture看做是资源,在你的测试用例执行之前需要去配置这些资源,执行完后需要去释放资源。比如module类型的fixture,适合于那些许多测试用例都只需要执行一次的操作。更多使用请参考这里

上面的两个测试方法都有 rect = Rectangle(30, 15) 实例化类, 此时可以使用 fixture 简化代码:

from rectangle import Rectangle
import pytest


@pytest.fixture()
def my_rect():
    rect = Rectangle(30, 15)
    return rect


def test_area(my_rect):
    assert my_rect.area() == 450


def test_perimeter(my_rect):
    assert my_rect.perimeter() == 90

3.2 标记用例

pytest.mark 定义在函数前面。可以给用例打标签,用于给用例分类与筛选用例。每个用例可以加多个标签。在执行用例时根据标签名选择执行。相比unittest 通过 TestSuite 加载不同的测试用例要方便的多。

from rectangle import Rectangle
import pytest


@pytest.fixture()
def my_rect():
    rect = Rectangle(30, 15)
    return rect


@pytest.mark.smoke
@pytest.mark.p1
def test_area(my_rect):
    assert my_rect.area() == 450


@pytest.mark.regression
def test_perimeter(my_rect):
    assert my_rect.perimeter() == 90

在运行之前, 需要先创建一个pytest.ini 文件在当前项目下,注册标签名。

[pytest]
markers=
    smoke
    p1
    regression

运行的时候加参数 -m 标签名, 就可以只执行带标签名的用例。

图片描述

3.3 测试数据参数化

你可能希望在预定义的数据集上运行测试。PyCharm 支持通过 @pytest.mark.parametrize 在 pytest 中实现的测试参数化。更多使用参考

增加下面的测试用例在 test_rectangle.py 文件中,传递三个参数两组数据给测试用例。如果数据可以用于其它用例,也可以定义数据做为全局变量。

@pytest.mark.parametrize(("length", "width", "expected_diff"), [(30, 20, 10), (20, 20, 0)])
def test_diff(length, width, expected_diff):
    rect = Rectangle(length, width)
    assert rect.diff() == expected_diff

点击编辑器侧边框绿色箭头执行上面的用例,可以看到用例执行了两次:

图片描述

3.4 重复运行用例

有时候需要重复运行单个用例,Pytest也提供相应的插件支持 。需要事先安装包 pytest-repeat, Preference/Settings -> Project -> Python Interpreter

图片描述

通过在函数前加 @pytest.mark.repeat(次数),指定函数重复的次数,也可以通过命令行–count=次数,为所有函数级别方法指定重复次数。

@pytest.mark.regression
@pytest.mark.repeat(5)
def test_perimeter(my_rect):
    assert my_rect.perimeter() == 90

执行测试用例,可以看到用例被执行了5 次:

图片描述

3.5 并发执行用例

有时候为了节省测试时间,需要并发执行测试用例。Pytest 是支持并发测试的,需提前安装包 pytest-xdist。运行的时候加参数 -n 并发进程数 或者 -n auto (根据当前cpu信息自动分配合理的核数运行用例。也可以在 pytest.ini 指定。

[pytest]
addopts = -n3

为了增强演示效果,在三个用例里都增加了10秒的延迟,并设置了三个并发,可以观察到三个用例是同时执行的。

图片描述

Tips:当使用这个插件做并发测试时,只能用于没有依赖关系的测试用例。

4. 小结

本节主要介绍了在 PyCharm 里如何使用 Pytest 测试框架和一些测试过程中常用插件。Pytest 是功能非常强大的测试框架,在业内也比较被推崇,如果想真正掌握,建议多参考官方文档及相关的插件说明文档,这将帮助我们在开发单元测试与自动化测试过程中事半功倍。
图片描述