3

Python项目的pytest初始化

 3 years ago
source link: https://note.qidong.name/2018/01/pytest-init/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Python项目的pytest初始化

2018-01-19 00:08:25 +08  字数:2310  标签: Python Test

为什么要写测试

Python是一种必须要写测试的语言。

和C/C++、Java这种需要编译的语言相比,Python这种动态语言天生就有开发迅速的优势。 开发完成后的代码,跑一跑自然知道问题。 然而,如果项目稍大,就难以保证手动测试的可靠性。 因此,江湖传言,Python不适合做大型项目。 当然,江湖上的大多数人是不写测试的。

与Java之类相比,没有100%测试覆盖的Python代码,甚至都不能保证没有语法错误。 而静态语言在预编译时,就会做完整的语法检查,可靠性的差距可见一斑。

然而,正是因为这种差距,Python项目有更迫切的测试需求。 达成100%测试覆盖率的Python项目,可靠性却又比没有写测试的Java项目要强上许多。

为什么是pytest

打开PyCharm,可以看到默认支持的单元测试框架有五类:

unittest是Python标准库之一。 与Java里著名的JUnit4非常类似,通过继承一个TestCase,然后增加test_*命名的method来测试。 (JUnit5中已经抛弃了这种做法,改为使用注解@Test的形式。) 它的问题就是,有些过于繁琐。

doctest是一种非常奇特的测试技术,在文档中写测试。 把代码和测试写在一起,这是Java这类语言中没有办法做到的。 虽然它也是Python标准库之一,但孤却第一时间放弃了。 对简单的数学函数,它是很便捷的;但对于复杂的、有副作用(side effect)的function或method来说,它功能不足。 而且,测试代码往往比功能代码更复杂些,不合适写在pydoc里。

Twisted是一个异步网络框架,而Trial则是其单元测试系统,是对unittest的一个封装。 如果不是使用Twisted框架,那么不会使用到它。

nose是一个曾经非常流行的测试,兼容unittest的同时却拥有更加简洁的语法,然而已经停止维护了。 nose本身支持Python 3,然而其很多插件却不支持。 新的项目,不推荐使用它,正如它的文档所说。 非常可惜,孤差点就选它了。

Nose has been in maintenance mode for the past several years and will likely cease without a new person/team to take over maintainership. New projects should consider using Nose2, py.test, or just plain unittest/unittest2.

最终,孤选择了pytest,曾用名py.test。 它能兼容unittestnose的测试代码,写法简洁,并且还有自己的独到之处。

在《PythonTestingToolsTaxonomy - Python Wiki》还可看到更多不同类型的测试框架。 本文仅介绍pytest测试最基本的写法,以及如何在一个Python项目进行初始化。

pytest测试样例

unittest类似的是,pytest也需要把测试function或method命名为test_*。 方便之处在于,不仅支持function,测试method所在的class也不需要继承什么。

此外,也不需要assert*系列的function来做检验,直接用内置的assert即可。

def add(a, b):
    return a + b


def test_add():
    assert 3 == add(1, 2)

假如以上代码文件名为add.py,写好后直接运行pytest add.py,即可开始测试。

测试代码结构

孤认为,测试代码不应该随着Python包而发布,尽管很多知名的包做出了相反的选择。 测试应该在打包发布前完成。 而发布后,用户是不需要、也不应该去跑测试的。 很多自带测试的包,甚至会干扰当前项目的测试结果。

而功能代码与测试代码的分离,通常采用以下形式:

project
├── setup.cfg
├── setup.py
├── src
│   └── mypkg
│       ├── __init__.py
│       └── something.py
└── tests
    ├── conftest.py
    ├── test_init.py
    └── test_something.py

源码放在src下,而测试则放在tests下。 不把mypkg直接放在根目录下,是为了避免直接从当前路径导入。 这样虽然方便,但有时会导致安装后的代码的未经测试的(通过测试覆盖率的一些异常可以观察到这一情况)。 详情可以查看这篇著名的博文:《Packaging a python library | ionel’s codelog》,它也是pytest官方文档所推荐的。

tests可以增加__init__.py,成为一个Python包,也可以增加子目录、子包。 conftest.pypytest的默认配置文件,可以在其中放公用的fixture或plugin。

setup.py与setup.cfg

setup.pytests_require中,需要配置pytest。 另外,也建议在setup_requires里配置pytest-runner

from setuptools import setup

setup(
    ...
    setup_requires=[
        'pytest-runner',
    ],
    tests_require=[
        'pytest',
    ],
)

即使不配置pytest,也可直接通过运行pytest命令来测试。 而做出以上配置后,可以通过python setup.py pytest命令来测试,并且不用提前安装pytest

如果需要通过python setup.py test的形式执行测试,则需要添加[aliases]setup.cfg

[aliases]
test=pytest

[tool:pytest]
addopts = --verbose
python_files = tests/*

setup.cfg中的[tool:pytest]块,就是对pytest的配置。 也可在pytest.initox.ini中,添加[pytest]块进行相同配置,效果一样。 不过,配在setup.cfg,可以在项目根目录少一个文件,孤更喜欢一些。

addoptspytest的命令行参数,--verbose会让打印输出更细致。 python_files指定了测试代码的位置。 有了这两个基本的配置,执行测试时就可以不用输入任何参数了。 后续如果有什么新的参数,也都可以配置到这里。

结语

一个项目的pytest初始化就是这样。 然后,就可以开始愉快地写测试了。

参考


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK