pytest 是什么?
pytest是python中非常流行的ut测试框架,这个框架有多流行呢? 应该说现在比较新的开源项目基本都已经使用pytest用来写UT了,比如我们熟悉的requests,ansible,flask, pypy
等库有兴趣的同学不妨去Github上看下对应的test目录下的测试文件,全部都是用pytest作为UT测试框架的。而且Pycharm,VSCode等IDE已经原生支持这个框架,只要在设置中将默认框架从unittest切换为pytest即可。并且这个框架在用户群体中也有非常高的评价,比如:
当然这个框架除了作为ut测试框架以外,用来做BDD或者E2E测试也是完全没有问题的。比如接口测试框架HttpRunner3.0就是直接包了一层pytest外加一些拓展实现的。
为什么pytest如此流行
笔者自己的经历来说,在接触pytest之前曾经在cucumber,behave,robotframework
之间徘徊不定。直到发现了pytest这个框架以后,感觉就是duang~ 用起来各种流畅。下面来简单介绍框架提供的几个核心特点:
-
用例写作非常简练,没有模版代码,非常pythonic。写作一个简单的用例跟普通的代码相差无几,例如:
def test_simple_case(): b = [2, 3, 4] assert 1 in b
运行测试只需要在同级目录下输入:
$ pytest
-
Assert重写,更加友好的错误信息,定位问题一目了然:
pytest
重载了默认的assert方法,你可以像书写普通的条件语句一样书写你的assert方法。再也不用记住其他框架里诸如AssertEqual, AssertContains
这些关键字了。并且在遇到Assertion
失败的时候,提供的上下文也非常简洁明了。比如l下面的assertion错误信息,可以非常直观的看到出错的地方在 index100的地方:def test_eq_list(): long_list = [0, 1, 2] * 100 a = long_list + [3, 4, 5] b = long_list + [4, 4, 5] > assert a == b E assert [0, 1, 2, 0, 1, 2, ...] == [0, 1, 2, 0, 1, 2, ...] E At index 300 diff: 3 != 4 E Use -v to get the full diff
-
Mark(类似其他框架里面的标签)功能:
Mark用来标记测试用例,用来方便的为测试用例添加属性信息,以方便插件根据这些熟悉信息作一些特殊处理。比如内置的
skip, skipif, xfail
等mark,就分别用来表示跳过,条件式跳过,预期内的失败用例。比如下面的例子,可以标记某些case在windows的环境上跳过执行等:import sys import pytest def test_normal_case(): """no skip mark function""" pass @pytest.mark.skip(reason="not implemented yet") def test_in_the_future(): raise NotImplemented # class-level mark @pytest.mark.skipif(sys.platform == 'win32', reason="not applicable on windows x86") class TestPosixCalls(object): def test_only_available_in_linux(self): """will not be setup or run under 'win32' platform"""
而一些其他插件比如pytest-repeat, pytest-timeout等,则分别使用
@pytest.mark.repeat()
和@pytest.mark.timeout()
来单独标记某些需要重复执行多次的用例以及用例执行执行超时控制(超时则停止执行并且标记为失败)。 -
Fixture(类似其他框架里的setup/teardown,依赖注入,共享实例等)系统:
-
使用
yield
声明测试前与测试后执行代码,语法非常简洁,例如:@pytest.fixture() def db_session(scope="session"): db = DBSession('scratch') db.connect() yield db db.close() def test_query_data(db_session): # do something with db_session here assert pass def test_update_data(db_session): # do something with db_session here assert pass
这里将
db_session
声明为一个fixture
,并且作为参数传入测试用例,用例就可以直接基于这个db_session
做操作。无需关心具体的数据库连接和断开时机。由于fixutre
是支持function,class,module,session
不同层级的共享,这里我们将scope设置为session,意思是这一轮测试里面,db.connec
t和db.close()
方便按需进行实例化: -
支持参数化用例声明,例如:
import pytest @pytest.mark.parametrize("test_input,expected", [ ("3+5", 8), ("2+4", 6), pytest.param("6*9", 42, marks=pytest.mark.xfail), ]) def test_eval(test_input, expected): assert eval(test_input) == expected
在实际执行时会得到类似如下的输出,也就是同样的步骤在不同的输入情况下的输出用例:
collected 3 items fixture/test_parametrize.py::test_eval[3+5-8] PASSED [ 33%] fixture/test_parametrize.py::test_eval[2+4-6] PASSED [ 66%] fixture/test_parametrize.py::test_eval[6*9-42] XFAIL [100%]
-
-
插件系统
作为测试框架,pytest有着非常好的插件系统设计。框架本身基于pluggy框架开发,通过调用定义良好的hooks来实现配置,收集,执行和报告这些过程。官方收录的插件列表就有300+,并且插件开发也比较容易上手,只需要了解一下框架提供的几个hook和一些类的属性,有兴趣的同学也可以看下笔者的个人博客的介绍。
注:有些内容因为截图比较多,大家可以结合附件的PPT一起看。这里就不再赘述了。限于篇幅以及笔者水平,还有一些高级的功能无法给到家娓娓道来,有兴趣的同学可以直接去官网看教程。
总的来说,笔者的感慨就是好的框架想要走的长远都需要在三个点上花功夫:
简单: 这个很大程度上决定你的受众有多少,也就是你能走多快的问题;
可靠:我们当然不会选择一个简单易出错或者一拓展就懵逼的这种框架,可靠的框架会在使用中不断积累用户口碑,吸引更多用户加入。
易拓展:这个基本上决定你能走多远,没有拓展性的框架一般寿命不会长久,很快就会被新的框架替代。pytest正是借助其良好的插件系统设计,拓展了许许多多非常实用的功能。毕竟,没人人比用户更懂自己的需求(乔布斯除外)。