Python 编程必不可少的测试框架“pytest篇”

  • 2020 年 2 月 25 日
  • 笔记

测试是为了更高效的完成功能实现。

pytest 是基于 unittest 实现的第三方测试框架,比 unittest 更加的简洁、高效,并且可以完美兼容 unittest 的测试代码,无需对其做任何的修改。

pytest 的使用

使用 pip install pytest 可以直接安装 pytest 测试框架。

pytest 通过装饰器“@pytest.fixture”将函数设置为固件,以便于在测试开始前和测试开始后执行相应的操作。在函数中通过 yield 将同一个函数分为两部分,分别在测试前和测试后执行,避免遗漏资源的释放。

pytest 通过 conftest.py 文件进行数据共享,在其它文件中无需导入即可使用。并且 pytest 会自动识别 conftest.py 文件,无需显示指定。可以为子文件夹单独设置 conftest.py 文件。

Python 编程必不可少的测试框架“unittest 篇” 中讲述了 unittest 测试框架的使用,在这里我们将上一篇中的测试使用 pytest 重新实现,来观察 unittest 和 pytest 的区别。

我们将所有的公共函数“固件”放入 conftest.py 文件中,文件内容大致如下:

DEFAULT_USERNAME = 'test'  DEFAULT_PASSWORD = 'test'    @pytest.fixture  def app():      db_fd, db_file = tempfile.mkstemp()      app = create_app()      app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///'+ db_file    print('pytest start')    with app.app_context():          db.drop_all()          db.create_all()          user = User.create(              name=DEFAULT_USERNAME,              password=DEFAULT_PASSWORD,              permission=Permission.ADMINISTRATOR,              active=True)    yield app  print('pytest stop')  with app.app_context():          db.session.remove()          db.drop_all()      os.close(db_fd)      os.unlink(db_file)    @pytest.fixture  def clinet(app):  return app.test_client()    @pytest.fixture  def headers(app, clinet):      rv = clinet.post('/api/v01/user/login',                          data=json.dumps(dict(user_name='test', password='test')),                          content_type='application/json')      data = json.loads(rv.data)      token = data['token']      headers = {"Authorization":"Bearer "+token, 'Content-Type': 'application/json'}    yield headers    pass

conftest.py 文件实现的内容实际上就是 unittest 中 setUpsetDown 函数的内容。整体实现上更加的简单明了。

在测试文件中可以直接将使用装饰器 @pytest.fixture 标记的函数以同名参数的方法传入测试函数中,即可在测试函数中使用相应的功能。同样以 login 和 add_user 两个功能的测试为例,实现在 pytest 框架的测试实现:

def test_login(clinet):      rv = clinet.post('/api/v01/user/login',                      data=json.dumps(dict(user_name='test', password='test')),                      content_type='application/json')      data = json.loads(rv.data)    assert rv.status_code == 200  assert data['status'] == 1  assert data['name'] == 'test'  assert data['token'] isnotNone  assert data['admin'] isnotNone  assert data['expire'] isnotNone    def test_add_user(clinet, headers):        rv = clinet.post('/api/v01/user',                      data=json.dumps(dict(user_name='123', password='123', admin=False)),                      headers=headers)      data = json.loads(rv.data)  assert rv.status_code == 200  assert data['status'] == 1

在 pytest 中使用 assert 加表达式的方法来对结果进行验证,而在 unittest 中要通过 assertEqual、assertIn、assertTrue、assertFalse 等等来完成,要记忆的更多实现也更复杂。

使用 pytest 来运行测试实例,可以看到如下结果

================================================================================ test session starts ================================================================================  platform darwin -- Python3.7.5, pytest-5.3.3, py-1.8.1, pluggy-0.13.1  rootdir: ***************  collected 4 items    tests/test_user.py ..                                                                                                                                                         [ 50%]  tests/unittest/test_user_unittest.py ..                                                                                                                                       [100%]

可以看到测试结果标记了测试进度,并且同步测试了 unittest 的测试用例。你可以通过 -s 参数来显示测试函数中的 print 输出内容。

如果你使用 -s 参数来 print 函数的输出的话,就会看到当前所有的固件“Fixture”在每个测试函数开始和完成时都会执行一次,这不是很浪费资源吗,是否可以每次测试运行只执行一次固件呢,答案是可以的,这就要用到固件的作用域了,通过装饰器 @pytest.fixture(scope='session') 来设置该固件的作用域是整个测试过程。更多内容请看文末的思维导图。

unittest 和 pytest 的比较

  • 固件“Fixture” 在 unittest 中通过固定的函数 setUp 和 tearDown 来实现测试用例的前置和后置函数,并且是针对所有测试用例的。而在 pytest 中通过装饰器来设置固件的函数命名方式更加的灵活,并且可以将固件设置为函数级、类级、模块级、以及全局级。pytest 以 conftest.py 作为默认配置实现全局数据共享。
  • 断言实现方式 在 unittest 中将每种判断方式单独实现了一个断言函数,比如 assertEqual、assertIn、assertTrue、assertFalse 等等,使用起来过于麻烦。在 pytest 中直接使用 assert + 表达式的方法来实现,更加清晰明了。
  • 参数化 unittest 本身没有实现参数化的功能,pytest 可以通过装饰器 @pytest.mark.parametrize 快速实现参数化。

pytest 知识点的思维导图:

公众号回复 Flask 获取相关源码!