pytest的简单学习

本文介绍了pytest单元测试框架,包括其下载安装、基础的assert断言、fixture方法的使用、mark类的应用,以及如何控制测试执行。fixture简化了资源管理,mark则提供了测试筛选功能。
摘要由CSDN通过智能技术生成

pytest的简单学习

一、介绍及下载安装

  • pytest是python的一种单元测试框架,与python自带的unittest测试框架类似,但是比unittest框架使用起来更简洁,效率更高。
  • 其主要使用assert断言对单元方法记性测试
  • 其有个fixture类,可以减少资源占用,资源的统一调度
  • 安装:pip install -U pytest
  • 测试: py.test --version或者pytest --version

二、基础单元测试(assert断言)

  • assert断言是python标准语法里的东西
  • asset后是一个返回布尔值的表达式
  • 若为真,则通过;若为假,抛出异常
# example
>>>assert 1 == 1
>>>assert 2+2 == 2*2
>>>assert len('hello') < 10
>>>assert len('hello') > 10
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError
>>>assert len('hello') > 10, '字符串长度小于10'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError: 字符串长度小于10
>>>assert range(4) == [0,1,2,3]

通过上面关于assert的例子,可以看出很适合单元测试。

1、测试函数的例子

def func(x):
    return x+1

def test_func():
    assert func(3) == 5

进入终端,在该文件所在目录下,执行pytest
执行结果如下:

========================= test session starts =========================
platform linux2 -- Python 2.7.13+, pytest-3.7.4, py-1.6.0, pluggy-0.7.1
rootdir: /home/light/code/study/test_pytest, inifile:
plugins: celery-4.2.1
collected 1 item                                                                                                        

test_pytest.py F                                                                                                  [100%]

========================= FAILURES =========================
_________________________ test_func ________________________

    def test_func():
>       assert func(3) == 5
E       assert 4 == 5
E        +  where 4 = func(3)

test_pytest.py:6: AssertionError
========================= 1 failed in 0.39 seconds =========================

pytest会在当前的目录下,寻找以test开头的文件(即测试文件),找到测试文件之后,进入到测试文件中寻找test_开头的测试函数并执行。
通过上面的测试输出,我们可以看到该测试过程中,收集到了一个测试函数,测试结果是失败的(标记为F),并且在FAILURES部分输出了详细的错误信息,帮助我们分析测试原因,我们可以看到”assert func(3) == 5”这条语句出错了,错误的原因是func(3)=4。

2、测试类的例子


class TestClass:
    def test_one(self):
        x = "this"
        assert 'h' in x

    def test_two(self):
        x = "hello"
        assert hasattr(x, 'check')

进入终端,在该文件所在目录下,执行pytest
执行结果如下:

========================= test session starts =========================
platform linux2 -- Python 2.7.13+, pytest-3.7.4, py-1.6.0, pluggy-0.7.1
rootdir: /home/light/code/study/test_pytest, inifile:
plugins: celery-4.2.1
collected 3 items                                                                                                       

test_pytest.py ..F                                                                                                [100%]

========================= FAILURES =========================
________________________ TestClass.test_three ________________________

self = <test_pytest.test_pytest.TestClass object at 0x7f9371aa09d0>

    def test_three(self):
        x = 'world'
>       assert hasattr(x, 'hello')
E       AssertionError: assert False
E        +  where False = hasattr('world', 'hello')

test_pytest.py:20: AssertionError
========================= 1 failed, 2 passed in 0.14 seconds =========================

pytest会在当前的目录下,寻找以test开头的文件(即测试文件),找到测试文件之后,进入到测试文件中寻找Test开头的测试类并执行。
通过观察上面的测试结果,可以看到采集到了三个测试函数,其中前两个成功(.表示),最后一个失败了(F表示),再看FAILURES部分,可以看到False = hasattr(‘world’, ‘hello’),也就是’world’没有‘hello‘属性,所以报错。

3、如何编写测试样例

通过前面两个例子,可以看出一些规律。
- 一般情况下,只需要在测试文件所在的文件夹下执行pytest就可以开始测试
- 测试文件以test_开头或者以_test结尾
- 测试函数以test_开头
- 测试类以Test开头,并且没有__init__方法
- 断言使用assert
- 满足以上条件,pytest就可以直接运行测试用例

三、fixture方法

  • pytest的fixture方法,本身可以理解为装饰器,它装饰一个函数,装饰后的函数可以被测试函数(test_开头的函数)作为参数使用。
  • 举个例子:有一个函数功能是打开某个文件,用fixture装饰。其他的测试函数将打开文件函数作为参数传入,就可以直接使用文件。
  • 通过上面的例子,可以把fixture看做是资源,在你的测试用例执行之前需要去配置这些资源,执行完后需要去释放资源。
  • 通过fixture方法,减少了资源的调用,通过配置scope、autouse等参数,可以更加简化测试流程。
  • fixture方法原型:def fixture(scope="function", params=None, autouse=False, ids=None, name=None):

1、fixture简单实例

import pytest

@pytest.fixture()
def before():
    print '\nbefore each test'

def test_1(before):
    print 'test_1()'

def test_2(before):
    print 'test_2()'
    assert 0

上面的代码用fixture方法装饰了before函数,before函数打印了一行数据。
两个测试函数的参数中有before。

进入终端,在该文件所在目录下,执行pytest -v -s。(备注:-v指的是将结果.映射为PASSED,将F映射为FAILED;-s是指执行代码过程)

========================= test session starts =========================
platform linux2 -- Python 2.7.13+, pytest-3.7.4, py-1.6.0, pluggy-0.7.1 -- /home/light/.virtualenvs/study/bin/python2.7
cachedir: .pytest_cache
rootdir: /home/light/code/study/test_pytest, inifile:
plugins: celery-4.2.1
collected 2 items                                                                                          

test_pytest.py::test_1 
before each test
test_1()
PASSED
test_pytest.py::test_2 
before each test
test_2()
FAILED

========================= FAILURES =========================
________________________ test_2 ________________________

before = None

    def test_2(before):
        print 'test_2()'
>       assert 0
E       assert 0

test_pytest.py:15: AssertionError
========================= 1 failed, 1 passed in 0.03 seconds =========================

从结果可以看到,两个测试函数在执行前打印了一行数据。
这个例子可能没什么意义,但假设before函数打开了一个文件或者打开了一个session,测试函数就可以直接使用。

2、三种调用fixture方式

测试函数直接调用
通过mark.usefixtures()调用
import pytest

@pytest.fixture()
def before():
    print('\nbefore each test')

@pytest.mark.usefixtures("before")
def test_1():
    print('test_1()')

@pytest.mark.usefixtures("before")
def test_2():
    print('test_2()')

class Test1:
    @pytest.mark.usefixtures("before")
    def test_3(self):
        print('test_1()')

    @pytest.mark.usefixtures("before")
    def test_4(self):
        print('test_2()')

@pytest.mark.usefixtures("before")
class Test2:
    def test_5(self):
        print('test_1()')

    def test_6(self):
        print('test_2()')
通过autouse调用
import time
import pytest

@pytest.fixture(scope="module", autouse=True)
def mod_header(request):
    print('\n-----------------')
    print('module      : %s' % request.module.__name__)
    print('-----------------')

@pytest.fixture(scope="function", autouse=True)
def func_header(request):
    print('\n-----------------')
    print('function    : %s' % request.function.__name__)
    print('time        : %s' % time.asctime())
    print('-----------------')

def test_one():
    print('in test_one()')

def test_two():
    print('in test_two()')

autouse直译自动调用,它需要和scope进行配合。
- fixture方法原型:def fixture(scope="function", params=None, autouse=False, ids=None, name=None):
scope代表范围,autouse代表是否自动调用。
- scope

function:每个test都运行,默认是functionscope
class:每个class的所有test只运行一次
module:每个module的所有test只运行一次
session:每个session只运行一次,这里的session指的是pytest跑一次的窗口,是最大范围

3、fixture返回值

在上面的例子中,fixture返回值都是默认None,我们可以选择让fixture返回我们需要的东西。如果你的fixture需要配置一些数据,读个文件,或者连接一个数据库,那么你可以让fixture返回这些数据或资源。

如何带参数
fixture还可以带参数,可以把参数赋值给params,默认是None。对于param里面的每个值,fixture都会去调用执行一次,就像执行for循环一样把params里的值遍历一次。
test_fixture_param.py

import pytest

@pytest.fixture(params=[1, 2, 3])
def test_data(request):
    return request.param

def test_not_2(test_data):
    print('test_data: %s' % test_data)
    assert test_data != 2
========================= test session starts =========================
platform linux2 -- Python 2.7.13+, pytest-3.7.4, py-1.6.0, pluggy-0.7.1 -- /home/light/.virtualenvs/study/bin/python2.7
cachedir: .pytest_cache
rootdir: /home/light/code/study/test_pytest, inifile:
plugins: celery-4.2.1
collected 3 items                                                                                         

test_param.py::test_not_2[1] test_data: 1
PASSED
test_param.py::test_not_2[2] test_data: 2
FAILED
test_param.py::test_not_2[3] test_data: 3
PASSED

========================= FAILURES =========================

可以看到三个参数分别执行。

4、测试数据库连接的例子

# -*-coding:utf-8 -*-
# 数据库表结构原型
# 存为Models.py

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String


Base = declarative_base()


class User(Base):
    __tablename__ = 'User'
    id = Column('id', Integer, primary_key=True, autoincrement=True)
    name = Column('name', String(50), unique=True)
    age = Column('age', Integer)

    def __repr__(self):
        return "<User(id='%s' name='%s' age='%s')>" % (
            self.id, self.name, self.age)


class Role(Base):
    __tablename__ = 'Role'
    id = Column('id', Integer, primary_key=True, autoincrement=True)
    name = Column('name', String(50), unique=True)

    def __repr__(self):
        return "<Role(id='%s' name='%s')>" % (self.id, self.name)
# -*-coding:utf-8 -*-
# fixture打开一个数据库,并做一些操作

# -*- coding:utf-8 -*-
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from Models import Base, User, Role
import pytest


@pytest.fixture(scope='module', autouse=True)
def connect():
    """
    连接到数据库,并且返回一个session连接
    启动pytest时自动调用
    """
    print('conn...')
    conn_str = 'mysql+mysqlconnector://root:password@localhost:3306/pytest'
    engine = create_engine(conn_str, echo=True)
    db_session = sessionmaker(bind=engine)
    Base.metadata.create_all(engine)
    session = db_session()
    return session


@pytest.fixture(scope='function', autouse=True)
def start_test():
    print('\nstart test...')


def test_delete_data(connect):
    """使用第一种方式调用fixture"""
    session = connect
    session.query(User).delete()
    session.query(Role).delete()
    assert session.query(User).all() == []
    assert session.query(Role).all() == []


def test_add_data(connect):
    u1 = User(name='Light', age=2)
    u2 = User(name='Ash', age=20)
    r = Role(name='user')
    session = connect
    session.add(u1)
    session.add(u2)
    session.add(r)
    session.commit()
    assert session.query(User).all() != []
    assert session.query(Role).all() != []


def test_select_data(connect):
    session = connect
    data1 = session.query(User).filter(User.id == '1').first()
    data2 = session.query(User).filter(User.id == '2').first()
    data3 = session.query(Role).filter(Role.id == '1').first()
    assert data1.name == 'Light' and data1.age == 23
    assert data2.name == 'Ash' and data2.age == 20
    assert data3.name == 'user'


def test_drop_table(connect):
    session = connect
    session.execute("drop table User")
    session.execute("drop table Role")
    assert session.execute('show tables').rowcount == 0

注意上面的代码,fixture使用了module级别的,并且autouse=True,通过执行过的结果可以看出,只在做第一个测试的时候连接了数据库,之后就没有连接过数据库。

进入终端对应文件夹,执行pytest -v test_session.py

========================= test session starts =========================
platform linux2 -- Python 2.7.13+, pytest-3.7.4, py-1.6.0, pluggy-0.7.1 -- /home/light/.virtualenvs/study/bin/python2.7
cachedir: .pytest_cache
rootdir: /home/light/code/study/test_pytest, inifile:
plugins: celery-4.2.1
collected 4 items                                                                                         

test_session.py::test_delete_data PASSED             [  25%]
test_session.py::test_add_data PASSED                [  50%]
test_session.py::test_select_data PASSED             [  75%]
test_session.py::test_drop_table PASSED              [ 100%]

========================= 4 passed in 1.06 seconds =========================

5、测试文件写入的例子

import pytest


@pytest.fixture(scope='module', autouse=True)
def openfile():
    print('open the file...')
    f = open('test.txt', 'a')
    return f


def test_write1(openfile):
    f = openfile
    old_tell = f.tell()
    data = '1111111111\n'
    f.write(data)
    new_tell = f.tell()
    assert new_tell - old_tell == len(data)


def test_write2(openfile):
    f = openfile
    old_tell = f.tell()
    data = '2222222222\n'
    f.write(data)
    new_tell = f.tell()
    assert new_tell - old_tell == len(data)


def test_close(openfile):
    openfile.close()
    with pytest.raises(ValueError) as e:
        openfile.write('3')

pytest -v -s test_file.py

=========================================== test session starts ===========================================
platform linux2 -- Python 2.7.13+, pytest-3.7.4, py-1.6.0, pluggy-0.7.1 -- /home/light/.virtualenvs/study/bin/python2.7
cachedir: .pytest_cache
rootdir: /home/light/code/study/test_pytest, inifile:
plugins: celery-4.2.1
collected 3 items                                                                                         

test_file.py::test_write1 open the file...
PASSED
test_file.py::test_write2 PASSED
test_file.py::test_close PASSED

======================================== 3 passed in 0.07 seconds =========================================

可以看到文件只打开了一次。

四、mark类

  • mark就是标记,也可以理解为一个装饰器。可以通过命令行参数指定特定的测试函数执行。
  • 在这里要说到pytest的命令行参数。pytest命令行参数非常多,在前面已经提到了-v(指的是将结果.映射为PASSED,将F映射为FAILED)和-s(指执行代码过程)。在这里先说-m和-k。
  • 也就是说,mark类主要的作用就是控制每个测试函数是否执行

1、简单的-m

import pytest


@pytest.mark.a
def test_a_1():
    print('this is a')


@pytest.mark.a
def test_a_2():
    print('this is a')


@pytest.mark.b
def test_b_1():
    print('this is b')

上面的代码里,分别标了两个a测试,一个b测试。

`pytest -v test_mark1.py -m a`
...
collected 3 items / 1 deselected
test_mark1.py::test_a_1 PASSED              [ 50%]
test_mark1.py::test_a_2 PASSED              [100%]
...

`pytest -v test_,ark1.py -m b`
...
collected 3 items / 2 deselected
test_mark1.py::test_b_1 PASSED              [100%]
...

可以看出, -m后面指定的字符会对应标记执行测试函数。

`pytest -v test_mark1.py -m "not a"`
...
collected 3 items / 2 deselected
test_mark1.py::test_b_1 PASSED              [100%]
...
`pytest -v test_mark1.py -m "not b"`
...
collected 3 items / 1 deselected
test_mark1.py::test_a_1 PASSED              [ 50%]
test_mark1.py::test_a_2 PASSED              [100%]
...
`pytest -v test_mark1.py -m "a or b"`
...
collected 3 items
test_mark1.py::test_a_1 PASSED              [ 33%]
test_mark1.py::test_a_2 PASSED              [ 66%]
test_mark1.py::test_b_1 PASSED              [100%]
...
`pytest -v test_mark1.py -m "a and b"`
...
collected 3 items / 3 deselected
...

通过上述命令行执行后的结果,可以看出pytest -m后面的参数可以被逻辑判断(not,and,or)。
这点很重要,因为-k也是如此。

2、唯一ID执行

假设只想执行某个测试函数,除此之外不执行,那么可以通过命令行指定。
pytest -v test_mark1.py::test_a_1

`pytest -v test_mark1.py::test_a_1`
...
collected 1 item
test_mark1.py::test_a_1 PASSED               [100%]
...

3、简单的-k

k指的是keyword,测试函数的关键字。假设不使用mark标记测试函数,也可以通过-k直接指定想测试的函数的名字或者部分名字。

`pytest -v test_mark1.py -k a_`
...
collected 3 items / 1 deselected
test_mark1.py::test_a_1 PASSED               [ 50%]
test_mark1.py::test_a_2 PASSED               [100%]
...
`pytest -v test_mark1.py -k "a_ or b_"`
...
collected 3 items
test_mark1.py::test_a_1 PASSED               [ 33%]
test_mark1.py::test_a_2 PASSED               [ 66%]
test_mark1.py::test_b_1 PASSED               [100%]
...
`pytest -v test_mark1.py -k "not a_"`
...
collected 3 items / 2 deselected
test_mark1.py::test_b_1 PASSED               [100%]
...

理解了-m的使用,-k的参数也是可以逻辑判断。

4、注册标记

五、hook类(留坑)

六、objects(留坑)

七、更多

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值