pytest随手记-fixture,参数化parametrizing、skip跳过用例

这篇笔记使用了上海-悠悠博客中的例子,详见https://www.cnblogs.com/yoyoketang,仅供自己查阅

本篇涉及fixture,参数化parametrizing,终结函数、fixture接受parametrizing参数、skip跳过用例

fixture

pytest有setup和teardown的用法,不过更推荐使用fixture的方式,优势在于:

  1. 命名方式灵活,不局限于setup和teardown这几个命名;
  2. 可以控制某些用例进行setup和teardown,因为有的用例并不需要或不应该进行setup和teardown;
  3. conftest.py 配置里可以实现数据共享,不需要import就能自动找到一些配置;
  4. scope="module" 可以实现多个.py跨文件共享前置, 每一个.py文件调用一次;
  5. scope="session" 以实现多个.py跨文件使用一个session来完成多个用例;
    fixture参数传入举个栗子(使用上海悠悠的栗子)
import pytest

# 不带参数时默认scope="function"
@pytest.fixture()
def login():
    print("输入账号,密码先登录")

def test_s1(login):
    print("用例1:登录之后其它动作111")

def test_s2():  # 不传login
    print("用例2:不需要登录,操作222")

def test_s3(login):
    print("用例3:登录之后其它动作333")

if __name__ == "__main__":
    pytest.main(["-s", "test_fix.py"])
-----------------------输出------------------------
platform win32 -- Python 3.6.0, pytest-3.6.3, py-1.5.4, pluggy-0.6.0
rootdir: E:\YOYO, inifile:
collected 3 items

test_fix.py 输入账号,密码先登录
用例1:登录之后其它动作111
.用例2:不需要登录,操作222
.输入账号,密码先登录
用例3:登录之后其它动作333

conftest.py配置

conftest.py配置好后可以使setup和teardown的操作生效于文件所在同一个package下的用例中
注意点

  • conftest.py配置脚本名称是固定的,不能改名称
  • conftest.py与运行的用例要在同一个pakage下,并且有init.py文件
  • 不需要import导入 conftest.py,pytest用例会自动查找
    上海悠悠的栗子
# conftest.py
    # coding:utf-8
    import pytest

    @pytest.fixture()
    def login():
        print("输入账号,密码先登录")

test_fix1.py
    # coding:utf-8
    import pytest
    
    def test_s1(login):
        print("用例1:登录之后其它动作111")
    
    def test_s2():  # 不传login
        print("用例2:不需要登录,操作222")
    
    def test_s3(login):
        print("用例3:登录之后其它动作333")
    
    if __name__ == "__main__":
        pytest.main(["-s", "test_fix1.py"])

test_fix2.py
    # coding:utf-8
    import pytest
    
    def test_s4(login):
        print("用例4:登录之后其它动作111")
    
    def test_s5():  # 不传login
        print("用例5:不需要登录,操作222")
    
    if __name__ == "__main__":
        pytest.main(["-s", "test_fix2.py"])

单独运行test_fix1.py和test_fix2.py都能调用到login()方法

终结函数关闭浏览器

yield和addfinalizer方法都是在测试完成后呼叫相应的代码,我理解都是用例执行前先执行yield和addfinalizer方法前的部分,在用例都执行完毕后开始执行yield和addfinalizer后的语句。

@pytest.fixture(scope='session', autouse=True)
def browser(request):
    global driver
    if driver is None:
        driver = webdriver.Firefox()

    def end():
        driver.quit()
    request.addfinalizer(end)                     
    return driver

调用fixture的三种方法

  1. 函数或类里面方法直接传fixture的函数参数名称
    例如:
# content of test_06.py
import time
import pytest

# ** 作者:上海-悠悠 QQ交流群:588402570**

@pytest.fixture(scope="function")
def start(request):
    print('\n-----开始执行function----')


def test_a(start):   # 在每个用例中传入形参start
    print("-------用例a执行-------")


class Test_aaa():   

    def test_01(self, start):     # 在每个用例中传入形参start
        print('-----------用例01--------------')

    def test_02(self, start):     # 在每个用例中传入形参start
        print('-----------用例02------------')

if __name__ == "__main__":
    pytest.main(["-s", "test_06.py"])
  1. 使用装饰器@pytest.mark.usefixtures()修饰
# content of test_07.py
import time
import pytest

# ** 作者:上海-悠悠 QQ交流群:588402570**

@pytest.fixture(scope="function")
def start(request):
    print('\n-----开始执行function----')


@pytest.mark.usefixtures("start")   # 每个用例都用@pytest.mark.usefixtures()修饰
def test_a():
    print("-------用例a执行-------")

@pytest.mark.usefixtures("start")    # 每个用例都用@pytest.mark.usefixtures()修饰,只需要在类上修饰
class Test_aaa():

    def test_01(self):
        print('-----------用例01--------------')

    def test_02(self):
        print('-----------用例02------------')

if __name__ == "__main__":
    pytest.main(["-s", "test_07.py"])
  1. autouse=True自动使用
  • start设置scope为module级别,在当前.py用例模块只执行一次,autouse=True自动使用
  • open_home设置scope为function级别,每个用例前都调用一次,自动使用
# content of test_08.py
import time
import pytest

# ** 作者:上海-悠悠 QQ交流群:588402570**

@pytest.fixture(scope="module", autouse=True)        # 在当前py文件执行一次
def start(request):
    print('\n-----开始执行moule----')
    print('module      : %s' % request.module.__name__)
    print('----------启动浏览器---------')
    yield
    print("------------结束测试 end!-----------")


@pytest.fixture(scope="function", autouse=True)       # 在每个用例执行一次
def open_home(request):
    print("function:%s \n--------回到首页--------" % request.function.__name__)


def test_01():
    print('-----------用例01--------------')

def test_02():
    print('-----------用例02------------')

if __name__ == "__main__":
    pytest.main(["-s", "test_08.py"])

也可在类中使用

import time
import pytest

# ** 作者:上海-悠悠 QQ交流群:588402570**

@pytest.fixture(scope="module", autouse=True)
def start(request):
    print('\n-----开始执行moule----')
    print('module      : %s' % request.module.__name__)
    print('----------启动浏览器---------')
    yield
    print("------------结束测试 end!-----------")


class Test_aaa():
    @pytest.fixture(scope="function", autouse=True)
    def open_home(self, request):
        print("function:%s \n--------回到首页--------" % request.function.__name__)


    def test_01(self):
        print('-----------用例01--------------')

    def test_02(self):
        print('-----------用例02------------')

if __name__ == "__main__":
    pytest.main(["-s", "test_09.py"])
  • 如果用例需要用到多个fixture的返回数据,fixture也可以return一个元组、list或字典,然后从里面取出对应数据;
  • 当然也可以分开定义成多个fixture,然后test用例传多个fixture参数;
  • fixture与fixture直接也能互相调用的,这条举个例子。
import pytest

@pytest.fixture()
def first():
    print("获取用户名")
    a = "yoyo"
    return a

@pytest.fixture()
def sencond(first):        # 这里传入上一个被修饰的函数名
    '''psw调用user fixture'''
    a = first
    b = "123456"
    return (a, b)

def test_1(sencond):
    '''用例传fixture'''
    print("测试账号:%s, 密码:%s" % (sencond[0], sencond[1]))

    assert sencond[0] == "yoyo"

if __name__ == "__main__":
    pytest.main(["-s", "test_fixture6.py"])

参数化parametrizing

pytest.mark.parametrize可以实现测试用例的参数化,可以理解为这个装饰器相当于将参数都传进来后依次for循环执行,注意:

  1. 该装饰器中传递两个参数,第一个参数是字符串,若有多个参数则用逗号分开
  2. 第二个参数为list,当有多组数据时用每组数据以元祖形式存放
  3. 装饰器修饰的函数使用的形参名要与该装饰器中第一个参数的形参名一致
    多说无益,还是看例子
# 这里是一个实现检查一定的输入和期望输出测试功能的典型例子
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

if __name__ == "__main__":
    pytest.main(["-s", "test_canshu1.py"])
-----------------------------输出结果-------------------------------
================================== 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_canshu1.py:11: AssertionError
===================== 1 failed, 2 passed in 1.98 seconds ======================

也可以标记单个测试实例参数化,例如使用内置的mark.xfail

# content of test_expectation.py
import pytest
@pytest.mark.parametrize("test_input,expected", [
                        ("3+5", 8),
                        ("2+4", 6),
                        pytest.param("6 * 9", 42, marks=pytest.mark.xfail),   # 这里标记了预测失败,因此在执行完毕不会报错,只会返回xfail
                        ])
def test_eval(test_input, expected):
    print("-------开始用例------")
    assert eval(test_input) == expected



if __name__ == "__main__":
    pytest.main(["-s", "test_canshu1.py"])
------------------------------输出--------------------------
test_canshu1.py -------开始用例------
.-------开始用例------
.-------开始用例------
x

===================== 2 passed, 1 xfailed in 1.84 seconds =====================

若要对参数进行组合则堆叠装饰器

import pytest
@pytest.mark.parametrize("x", [0, 1])
@pytest.mark.parametrize("y", [2, 3])
def test_foo(x, y):
    print("测试数据组合:x->%s, y->%s" % (x, y))


if __name__ == "__main__":
    pytest.main(["-s", "test_canshu1.py"])
-----------------------输出-----------------------
test_canshu1.py 测试数据组合:x->0, y->2
.测试数据组合:x->1, y->2
.测试数据组合:x->0, y->3
.测试数据组合:x->1, y->3
.

========================== 4 passed in 1.75 seconds ===========================

给参数意义

import pytest

data_1 = [
    pytest.param(1,2,3, id='(a+b):pass'),  # id自己定义,方便理解用例意义
    pytest.param(4,5,10, id='(a+b):fail')
]

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


class TestParametrize():
    @pytest.mark.parametrize('a, b, expect', data_1)
    def test_parametrize_1(self,a ,b, expect):
        assert add(a, b) == expect


if __name__ == '__main__':
    pytest.main(['-vs', 'testcase03.py'])
-----------------输出-----------------------
testcase03.py::TestParametrize::test_parametrize_1[(a+b):pass] PASSED
testcase03.py::TestParametrize::test_parametrize_1[(a+b):fail] FAILED

request参数
若@pytest.fixture装饰器中一些操作想要用到parametrize中的参数,传参得使用默认的request参数。使用request.param接收参数,举个例子

import pytest

#** 作者:上海-悠悠 QQ交流群:588402570**

# 测试账号数据
test_user_data = ["admin1", "admin2"]

@pytest.fixture(scope="module")
def login(request):      # 使用request形参
    user = request.param
    print("登录账户:%s"%user)
    return user

@pytest.mark.parametrize("login", test_user_data, indirect=True)   # 添加indirect=True参数是为了把login当成一个函数去执行,而不是一个参数
def test_login(login):
    '''登录用例'''
    a = login
    print("测试用例中login的返回值:%s" % a)
    assert a != ""


if __name__ == "__main__":
    pytest.main(["-s", "test_02.py"])
---------------------------输出-------------------------
..\..\..\..\..\..\YOYO\marktest\test_02.py 登录账户:admin1
测试用例中login的返回值:admin1
.登录账户:admin2
测试用例中login的返回值:admin2

request传2个参数
如果用到@pytest.fixture,里面用2个参数情况,可以把多个参数用一个字典去存储,这样最终还是只传一个参数,不同的参数再从字典里面取对应key值就行,如: user = request.param["user"]
举个例子

# coding:utf-8
import pytest

# ** 作者:上海-悠悠 QQ交流群:588402570**

# 测试账号数据
test_user_data = [{"user": "admin1", "psw": "111111"},
                  {"user": "admin1", "psw": ""}]

@pytest.fixture(scope="module")
def login(request):
    user = request.param["user"]
    psw = request.param["psw"]
    print("登录账户:%s" % user)
    print("登录密码:%s" % psw)
    if psw:
        return True
    else:
        return False

# indirect=True 声明login是个函数
@pytest.mark.parametrize("login", test_user_data, indirect=True)
def test_login(login):
    '''登录用例'''
    a = login
    print("测试用例中login的返回值:%s" % a)
    assert a, "失败原因:密码为空"


if __name__ == "__main__":
    pytest.main(["-s", "test_03.py"])
--------------------------输出------------------------
..\..\..\..\..\..\YOYO\marktest\test_03.py 登录账户:admin1
登录密码:111111
测试用例中login的返回值:True
.登录账户:admin1
登录密码:
测试用例中login的返回值:False
F
login = False

    @pytest.mark.parametrize("login", test_user_data, indirect=True)
    def test_login(login):
        '''登录用例'''
        a = login
        print("测试用例中login的返回值:%s" % a)
>       assert a, "失败原因:密码为空"
E       AssertionError: 失败原因:密码为空
E       assert False

D:\YOYO\marktest\test_03.py:25: AssertionError


================================== FAILURES ===================================
_____________________________ test_login[login1] ______________________________

login = False

    @pytest.mark.parametrize("login", test_user_data, indirect=True)
    def test_login(login):
        '''登录用例'''
        a = login
        print("测试用例中login的返回值:%s" % a)
>       assert a, "失败原因:密码为空"
E       AssertionError: 失败原因:密码为空
E       assert False

D:\YOYO\marktest\test_03.py:25: AssertionError
===================== 1 failed, 1 passed in 0.05 seconds ======================

多个fixtrue
上面的用例是可以同时放多个fixture的,也就是多个前置操作,可以支持装饰器叠加,使用parametrize装饰器叠加时,用例组合是2个参数个数相乘。
举个例子

# coding:utf-8
import pytest

# 测试账号数据
test_user = ["admin1", "admin2"]
test_psw = ["11111", "22222"]


@pytest.fixture(scope="module")
def input_user(request):
    user = request.param
    print("登录账户:%s" % user)
    return user


@pytest.fixture(scope="module")
def input_psw(request):
    psw = request.param
    print("登录密码:%s" % psw)
    return psw



@pytest.mark.parametrize("input_user", test_user, indirect=True)
@pytest.mark.parametrize("input_psw", test_psw, indirect=True)
def test_login(input_user, input_psw):
    '''登录用例'''
    a = input_user
    b = input_psw
    print("测试数据a-> %s, b-> %s" % (a,b))
    assert b

if __name__ == "__main__":
    pytest.main(["-s", "test_04.py"])
-------------------------输出---------------------------
collected 4 items

test_04.py 登录账户:admin1
登录密码:11111
测试数据a-> admin1, b-> 11111
.登录账户:admin2
测试数据a-> admin2, b-> 11111
.登录密码:22222
测试数据a-> admin2, b-> 22222
.登录账户:admin1
测试数据a-> admin1, b-> 22222
========================== 4 passed in 0.05 seconds ===========================

skip跳过用例

  1. 无条件地跳过模块中的所有测试:

pytestmark = pytest.mark.skip("all tests still WIP")

  1. 根据某些条件跳过模块中的所有测试

pytestmark = pytest.mark.skipif(sys.platform == "win32", "tests for linux˓→ only"

  1. 如果缺少某些导入,则跳过模块中的所有测试

pexpect = pytest.importorskip("pexpect")

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容

  • 一、pytest单元测试框架 1、什么是单元测试框架 单元测试框架是指在软件测试开发当中,针对软件的最小单位(函数...
    JaydenGoh阅读 517评论 0 2
  • 引言 如果你想快速上手pytest,只关注"Pytest必会知识点"章节就可以了(该章节已经能够解决基础的ui和接...
    测试开发Kevin阅读 1,579评论 0 15
  • pytest fixtures的目的是提供一个固定的基线,使测试可以在此基础上可靠地、重复地执行;对比xUnit经...
    luizyao阅读 504评论 0 0
  • Pytest 入门学习 pytest是一个非常成熟的全功能的Python测试框架,主要特点有以下几点: 简单灵活,...
    小娟_bb93阅读 2,436评论 0 5
  • 本文参考了官方文档和一些乐于分享的大佬的博客,结合自己的理解完成。学习pytest框架的小白,需要按照教程自己敲一...
    成都_阿木木阅读 14,208评论 1 5