欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

pytest学习0.0

程序员文章站 2022-03-11 16:16:49
...

1. easy start

直接运行pytest

# content of test_sample.py
def inc(x):
    return x + 1

def test_answer():
    assert inc(3) == 5
$ pytest
========== test session starts ========
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR
collected 1 item

test_sample.py F                                                     [100%]

============= FAILURES =============
___________ test_answer _____________

    def test_answer():
>       assert inc(3) == 5
E       assert 4 == 5
E        +  where 4 = inc(3)

test_sample.py:6: AssertionError
========== 1 failed in 0.12 seconds ==========

运行多个文件

没有定制的默认情况下的缺省规则

  • pytest会找当前以及递查找子文件夹下面所有的test_*.py*_test.py的文件,把其当作测试文件
  • 在这些文件里,pytest会收集下面的一些函数或方法,当作测试用例
    • 不在类定义中的以test_开头的函数或方法
    • 在以Test开头的类中(不能包含__init__方法),以test_开头的方法
  • pytest也支持unittest模式的用例定义

2. Assert

Assert就是断言,每个测试用例都需要断言。

与unittest不同,pytest使用的是python自带的assert关键字来进行断言,大大降低了学习成本。

assert关键字后面可以接一个表达式,只要表达式的最终结果为True,那么断言通过,用例执行成功,否则用例执行失败。

3. Fixture

可以把Fixture理解为准备测试数据和初始化测试对象的阶段。

一般我们对测试数据和测试对象的管理有这样的一些场景

  • 所有用例开始之前初始化测试数据或对象
  • 所有用例结束之后销毁测试数据或对象
  • 每个用例开始之前初始化测试数据或对象
  • 每个用例结束之后销毁测试数据或对象
  • 在每个/所有module的用例开始之前初始化数据或对象
  • 在每个/所有module的用例开始之后销毁数据或对象

举例1

# content of ./test_smtpsimple.py
import pytest

@pytest.fixture
def smtp_connection():
    import smtplib
    return smtplib.SMTP("smtp.gmail.com", 587, timeout=5)

def test_ehlo(smtp_connection):
    response, msg = smtp_connection.ehlo()
    assert response == 250
    assert 0 # for demo purposes

这里test_ehlo需要smtp_connection 的fixture值,pytest将会发现并且调用@pytest.fixture 标志着smtp_connection fixture函数。

$ pytest test_smtpsimple.py
============ test session starts ===============
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR
collected 1 item

test_smtpsimple.py F                                                 [100%]

=========== FAILURES =================
___________ test_ehlo ________________

smtp_connection = <smtplib.SMTP object at 0xdeadbeef>

    def test_ehlo(smtp_connection):
        response, msg = smtp_connection.ehlo()
        assert response == 250
>       assert 0 # for demo purposes
E       assert 0

test_smtpsimple.py:11: AssertionError
======== 1 failed in 0.12 seconds ========

举例2

考虑一场景,需要判断用户的密码中包含简单密码,密码必须至少6位,满足6位后判断用户的密码不是password123或者password之类的弱密码。

我们将用户的信息导出成名为users.dev.json的文件,该文件如下所示

[
  {"name":"jack","password":"Iloverose"},
  {"name":"rose","password":"Ilovejack"},
  {"name":"tom","password":"password123"},
]
# test_user_password.py

import pytest
import json

class TestUserPassword(object):
    @pytest.fixture
    def users(self):
        return json.loads(open('./users.dev.json', 'r').read()) # 读取当前路径下的users.dev.json文件,返回的结果一个列表,其中是一个个dict
# [{'name': 'jack', 'password': 'Iloverose'}, {'name': 'rose', 'password': 'Ilovejack'}, {'name': 'tom', 'password': 'password123'}, {'name': 'mike', 'password': 'password'}, {'name': 'james', 'password': 'AGoodPasswordWordShouldBeLongEnough'}]
# <class 'list'>

    def test_user_password(self, users):
        # 遍历每条user数据
        for user in users:
            passwd = user['password']
            assert len(passwd) >= 6
            msg = "user %s has a weak password" %(user['name'])
            assert passwd != 'password', msg
            assert passwd != 'password123', msg

运行

pytest可以通过指定文件名的方式运行单个用例文件,这里我们只运行test_user_password.py文件

pytest test_user_password.py

运行结果

$ pytest test_user_password.py
============= test session starts ================
platform darwin -- Python 2.7.12, pytest-3.2.3, py-1.4.34, pluggy-0.4.0
rootdir: /Users/easonhan/code/testclass.net/src/pytest, inifile:
collected 1 item

test_user_password.py F

================ FAILURES ================
________________ TestUserPassword.test_user_password __________

self = <test_user_password.TestUserPassword object at 0x1046e3290>
users = [{'name': 'jack', 'password': 'Iloverose'}, {'name': 'rose', 'password': 'Ilovejack'}, {'name': 'tom', 'password': 'password123'}]

    def test_user_password(self, users):
        for user in users:
            passwd = user['password']
            assert len(passwd) >= 6
            msg = "user %s has a weak password" %(user['name'])
            assert passwd != 'password', msg
>           assert passwd != 'password123', msg
E           AssertionError: user tom has a weak password
E           assert 'password123' != 'password123'

test_user_password.py:14: AssertionError
=============== 1 failed in 0.03 seconds ============

分析

  • 使用@pytest.fixture装饰器可以定义feature
  • 在用例的参数中传递fixture的名称以便直接调用fixture,拿到fixture的返回值
  • 3个assert是递进关系,前1个assert断言失败后,后面的assert是不会运行的,因此重要的assert放到前面
  • E AssertionError: user tom has a weak password可以很容易的判断出是哪条数据出了问题,所以定制可读性好的错误信息是很必要的
  • 任何1个断言失败以后,for循环就会退出,所以上面的用例1次只能发现1条错误数据,换句话说任何1个assert失败后,用例就终止运行了

执行顺序

pytest是这样运行上面的用例的

  1. pytest找到以test_开头的方法,也就是test_user_password方法,执行该方法时发现传入的参数里有跟fixture users名称相同的参数
  2. pytest认定users是fixture,执行该fixture,读取json文件解析成dict实例
  3. test_user_password方法真正被执行,users fixture被传入到该方法

举例3

例2中任何1条测试数据导致断言不通过后测试用例就会停止运行,这样每次只能检查出1条不符合规范的数据,很多时候需要一次性把所有的不符合结果都测出来。

users.test.json文件:

[
  {"name":"jack","password":"Iloverose"},
  {"name":"rose","password":"Ilovejack"}
  {"name":"tom","password":"password123"},
  {"name":"mike","password":"password"},
 {"name":"james","password":"AGoodPasswordWordShouldBeLongEnough"}
]

参数化fixture

参数化fixture允许我们向fixture提供参数,参数可以是list,该list中有几条数据,fixture就会运行几次,相应的测试用例也会运行几次。

参数化fixture的语法是

@pytest.fixture(params=["smtp.gmail.com", "mail.python.org"])

其中len(params)的值就是用例执行的次数

在fixture的定义中,可以使用request.param来获取每次传入的参数,如下:

@pytest.fixture(scope="module",
                params=["smtp.gmail.com", "mail.python.org"])
def smtp(request):
    smtp = smtplib.SMTP(request.param, 587, timeout=5)
    yield smtp
    print ("finalizing %s" % smtp)
    smtp.close()
  • 上面的代码smtp fixture会执行2次
  • 第1次request.param == 'smtp.gmail.com'
  • 第2次request.param == 'mail.python.org'

实现用例

我们现在使用参数化fixtures来实现一次性检查出弱密码的用例。

# test_user_password_with_params.py

import pytest
import json
users = json.loads(open('./users.test.json', 'r').read())  # list

class TestUserPasswordWithParam(object):
    @pytest.fixture(params=users)
    def user(self, request):
        return request.param

    def test_user_password(self, user):
        passwd = user['password']
        assert len(passwd) >= 6
        msg = "user %s has a weak password" %(user['name'])
        assert passwd != 'password', msg
        assert passwd != 'password123', msg

上面的例子里,我们先把所有用户信息读到users变量里,注意users这时候是list类型,可以直接传入到fixture的params

运行

pytest test_user_password_with_params.py

结果

$ pytest test_user_password_with_params.py
================== test session starts ====================
platform darwin -- Python 2.7.12, pytest-3.2.3, py-1.4.34, pluggy-0.4.0
rootdir: /Users/easonhan/code/testclass.net/src/pytest, inifile:
collected 5 items

test_user_password_with_params.py ..FF.
====================== FAILURES =========================
______ TestUserPasswordWithParam.test_user_password[user2] ____

self = <test_user_password_with_params.TestUserPasswordWithParam object at 0x10de1d790>, user = {'name': 'tom', 'password': 'password123'}

    def test_user_password(self, user):
        passwd = user['password']
        assert len(passwd) >= 6
        msg = "user %s has a weak password" %(user['name'])
        assert passwd != 'password', msg
>       assert passwd != 'password123', msg
E       AssertionError: user tom has a weak password
E       assert 'password123' != 'password123'

test_user_password_with_params.py:15: AssertionError
_____ TestUserPasswordWithParam.test_user_password[user3]_______

self = <test_user_password_with_params.TestUserPasswordWithParam object at 0x10de1df50>, user = {'name': 'mike', 'password': 'password'}

    def test_user_password(self, user):
        passwd = user['password']
        assert len(passwd) >= 6
        msg = "user %s has a weak password" %(user['name'])
>       assert passwd != 'password', msg
E       AssertionError: user mike has a weak password
E       assert 'password' != 'password'

test_user_password_with_params.py:14: AssertionError
======== 2 failed, 3 passed in 0.05 seconds =============

总共运行了5个用例,3个成功,2个失败。

举例4

@pytest.mark.parametrize 装饰器可以让我们每次参数化fixture的时候传入多个项目,parametrize每次多个参数,更加灵活。

import pytest
@pytest.mark.parametrize("test_input,expected", [
  ("3+5", 8),
  ("2+4", 6),
  ("6*9", 42),
])
def test_eval(test_input, expected):
    assert eval(test_input) == expected

test_eval方法中传入了2个参数,就如同@pytest.mark.parametrize装饰器中定义的那样,因此简单理解,我们可以把parametrize装饰器想象成是数据表格,有表头(test_input,expected)以及具体的数据。

运行结果

$ pytest
======= test session starts ========
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 3 items

test_expectation.py ..F

======= FAILURES ========
_______ test_eval[6*9-42] ________

test_input = '6*9', expected = 42

    @pytest.mark.parametrize("test_input,expected", [
        ("3+5", 8),
        ("2+4", 6),
        ("6*9", 42),
    ])
    def test_eval(test_input, expected):
>       assert eval(test_input) == expected
E       AssertionError: assert 54 == 42
E        +  where 54 = eval('6*9')

test_expectation.py:8: AssertionError
======= 1 failed, 2 passed in 0.12 seconds ========

转载自:http://www.testclass.net/pytest

相关标签: pytest