【问题标题】:setup and teardown with py.test data driven testing使用 py.test 数据驱动测试进行设置和拆卸
【发布时间】:2018-09-12 00:58:38
【问题描述】:

我有很多测试基本上执行相同的操作但使用不同的数据,所以我想使用 pytest 来实现它们,我设法以典型的 junit 方式来实现,如下所示:

import pytest
import random

d = {1: 'Hi',2: 'How',3: 'Are',4:'You ?'}

def setup_function(function):
    print("setUp",flush=True)

def teardown_function(functions):
    print("tearDown",flush=True)

@pytest.mark.parametrize("test_input", [1,2,3,4])
def test_one(test_input):
    print("Test with data " + str(test_input))
    print(d[test_input])
    assert True

这给了我以下输出

C:\Temp>pytest test_prueba.py -s

============================== 测试会话开始 =============== =============== 平台 win32 -- Python 3.6.5、pytest-3.5.0、py-1.5.3、pluggy-0.6.0 根目录:C:\Temp,inifile: 收集了 4 件物品

test_prueba.py

设置

用数据 1 测试

.tearDown

设置

用数据 2 测试

如何

.tearDown

设置

用数据 3 测试

.tearDown

设置

用数据进行测试 4

你?

.tearDown

=========================== 4 在 0.03 秒内通过 ================ ===========

现在的问题是我还要在设置和拆卸中执行一些我需要访问 test_input 值的操作

有什么优雅的解决方案吗? 也许为了实现这一点,我应该以不同的方式使用参数化或设置拆卸? 如果是这种情况,有人可以举一个设置和拆卸参数化的数据驱动测试示例吗?

谢谢!!!

【问题讨论】:

  • 看看 Python 的with statement。这是你要找的吗?您基本上使用标准设置/拆卸模板创建一个类。另请参阅this 示例

标签: python pytest


【解决方案1】:

parameterize 在测试中更多地用于指定原始输入和预期输出。如果您需要访问设置中的参数,那么它更像是夹具的一部分而不是测试。

所以你不妨试试:

import pytest

d = {"good": "SUCCESS", "bad": "FAIL"}

def thing_that_uses_param(param):
    print("param is", repr(param))
    yield d.get(param)
    print("test done")

@pytest.fixture(params=["good", "bad", "error"])
def parameterized_fixture(request):
    param = request.param
    yield from thing_that_uses_param(param)

def test_one(parameterized_fixture):
    assert parameterized_fixture.lower() == "success"

哪些输出:

============================= test session starts =============================
platform win32 -- Python 3.5.1, pytest-3.4.0, py-1.5.2, pluggy-0.6.0 -- c:\Users\User\AppData\Local\Programs\Python\Python35-32\python.exe
cachedir: .pytest_cache
rootdir: C:\Users\User\Documents\python, inifile:

collecting ... collected 3 items

a.py::test_one[good] PASSED                                              [ 33%]
a.py::test_one[bad] FAILED                                               [ 66%]
a.py::test_one[error] FAILED                                             [100%]

================================== FAILURES ===================================
________________________________ test_one[bad] ________________________________

parameterized_fixture = 'FAIL'

    def test_one(parameterized_fixture):
>       assert parameterized_fixture.lower() == "success"
E       AssertionError: assert 'fail' == 'success'
E         - fail
E         + success

a.py:28: AssertionError
---------------------------- Captured stdout setup ----------------------------
param is 'bad'
-------------------------- Captured stdout teardown ---------------------------
test done
_______________________________ test_one[error] _______________________________

parameterized_fixture = None

    def test_one(parameterized_fixture):
>       assert parameterized_fixture.lower() == "success"
E       AttributeError: 'NoneType' object has no attribute 'lower'

a.py:28: AttributeError
---------------------------- Captured stdout setup ----------------------------
param is 'error'
-------------------------- Captured stdout teardown ---------------------------
test done
===================== 2 failed, 1 passed in 0.08 seconds ======================

但是,这要求您为可能要与夹具一起使用的每组参数创建一个参数化夹具。

您也可以混合和匹配参数化标记和读取这些参数的夹具,但这需要测试使用参数的特定名称。它还需要确保这些名称是唯一的,这样它就不会与任何其他试图做同样事情的装置冲突。例如:

import pytest

d = {"good": "SUCCESS", "bad": "FAIL"}

def thing_that_uses_param(param):
    print("param is", repr(param))
    yield d.get(param)
    print("test done")

@pytest.fixture
def my_fixture(request):
    if "my_fixture_param" not in request.funcargnames:
        raise ValueError("could use a default instead here...")
    param = request.getfuncargvalue("my_fixture_param")
    yield from thing_that_uses_param(param)

@pytest.mark.parametrize("my_fixture_param", ["good", "bad", "error"])
def test_two(my_fixture, my_fixture_param):
    assert my_fixture.lower() == "success"

哪些输出:

============================= test session starts =============================
platform win32 -- Python 3.5.1, pytest-3.4.0, py-1.5.2, pluggy-0.6.0 -- c:\Users\User\AppData\Local\Programs\Python\Python35-32\python.exe
cachedir: .pytest_cache
rootdir: C:\Users\User\Documents\python, inifile:
collecting ... collected 3 items

a.py::test_two[good] PASSED                                              [ 33%]
a.py::test_two[bad] FAILED                                               [ 66%]
a.py::test_two[error] FAILED                                             [100%]

================================== FAILURES ===================================
________________________________ test_two[bad] ________________________________

my_fixture = 'FAIL', my_fixture_param = 'bad'

    @pytest.mark.parametrize("my_fixture_param", ["good", "bad", "error"])
    def test_two(my_fixture, my_fixture_param):
>       assert my_fixture.lower() == "success"
E       AssertionError: assert 'fail' == 'success'
E         - fail
E         + success

a.py:25: AssertionError
---------------------------- Captured stdout setup ----------------------------
param is 'bad'
-------------------------- Captured stdout teardown ---------------------------
test done
_______________________________ test_two[error] _______________________________

my_fixture = None, my_fixture_param = 'error'

    @pytest.mark.parametrize("my_fixture_param", ["good", "bad", "error"])
    def test_two(my_fixture, my_fixture_param):
>       assert my_fixture.lower() == "success"
E       AttributeError: 'NoneType' object has no attribute 'lower'

a.py:25: AttributeError
---------------------------- Captured stdout setup ----------------------------
param is 'error'
-------------------------- Captured stdout teardown ---------------------------
test done
===================== 2 failed, 1 passed in 0.08 seconds ======================

【讨论】:

  • 您好,谢谢您的回答;我按照您的第二个示例进行了操作,但是您介意解释一下吗? yield 和 yield from 是什么意思? (我刚在 Python 工作了几个月,我来自 Jave/C#/Powershell 背景)再次感谢您的回答
【解决方案2】:

我认为您正在寻找的是产量固定装置, 您可以在每次测试之前和之后制作一个 auto_use 固定装置 您可以访问所有测试元数据(标记、参数等) 你可以阅读它 here

并且对参数的访问是通过称为请求的函数参数

【讨论】:

  • 这听起来有点方向(尽管我希望仍然有一种简单的方法,比如 JUnit 设置拆卸,但可以访问数据驱动参数的当前测试)如何将这些夹具与数据驱动?
  • 请注意,yield_fixture 装饰器已被弃用,请使用 yieldpytest.readthedocs.io/en/latest/…
【解决方案3】:

IMO、set_up 和 tear_down 不应访问 test_input 值。如果您希望这样,那么您的测试逻辑可能存在一些问题。

set_up 和 tear_down 必须独立于测试使用的值。但是,您可以使用另一个夹具来完成任务。

【讨论】:

  • 我同意你的第一点,但如果你用例子详细说明“另一个夹具”会有很大帮助。
【解决方案4】:

我有很多测试基本上执行相同的操作但使用不同的数据

除了 Dunes' answer 完全依赖 pytest 之外,你的这部分问题让我觉得 pytest-cases 也可能对你有用。特别是如果一些测试数据应该参数化,而另一些则不需要。

例如,请参阅this other post,当然还有the documentation。顺便说一句,我是作者;)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-28
    • 1970-01-01
    • 2012-05-18
    • 1970-01-01
    • 2022-10-19
    相关资源
    最近更新 更多