跳转到主要内容

BDD for pytest

项目描述

https://img.shields.io/pypi/v/pytest-bdd.svg https://codecov.io/gh/pytest-dev/pytest-bdd/branch/master/graph/badge.svg https://github.com/pytest-dev/pytest-bdd/actions/workflows/main.yml/badge.svg Documentation Status

pytest-bdd 实现了 Gherkin 语言的子集,以实现项目需求测试的自动化,并促进行为驱动开发。

与其他许多BDD工具不同,它不需要单独的运行器,并受益于pytest的强大和灵活性。它能够统一单元测试和功能测试,减少持续集成服务器配置的负担,并允许重用测试设置。

为单元测试编写的pytest fixtures可以用于特征步骤中的设置和操作,并实现依赖注入。这允许真正的BDD“恰好足够”的需求规范,而不需要维护包含Gherkin命令声明副作用的环境对象。

安装pytest-bdd

pip install pytest-bdd

示例

一个博客托管软件的示例测试可能看起来像这样。请注意,这里使用了pytest-splinter来获取浏览器.fixture。

# content of publish_article.feature

Feature: Blog
    A site where you can publish your articles.

    Scenario: Publishing the article
        Given I'm an author user
        And I have an article

        When I go to the article page
        And I press the publish button

        Then I should not see the error message
        And the article should be published  # Note: will query the database

请注意,每个特性文件只允许一个特性。

# content of test_publish_article.py

from pytest_bdd import scenario, given, when, then

@scenario('publish_article.feature', 'Publishing the article')
def test_publish():
    pass


@given("I'm an author user")
def author_user(auth, author):
    auth['user'] = author.user


@given("I have an article", target_fixture="article")
def article(author):
    return create_test_article(author=author)


@when("I go to the article page")
def go_to_article(article, browser):
    browser.visit(urljoin(browser.url, '/manage/articles/{0}/'.format(article.id)))


@when("I press the publish button")
def publish_article(browser):
    browser.find_by_css('button[name=publish]').first.click()


@then("I should not see the error message")
def no_error_message(browser):
    with pytest.raises(ElementDoesNotExist):
        browser.find_by_css('.message.error').first


@then("the article should be published")
def article_is_published(article):
    article.refresh()  # Refresh the object in the SQLAlchemy session
    assert article.is_published

场景装饰器

使用scenario装饰器装饰的函数就像一个普通的测试函数一样,它们将在所有场景步骤之后执行。

from pytest_bdd import scenario, given, when, then

@scenario('publish_article.feature', 'Publishing the article')
def test_publish(browser):
    assert article.title in browser.html

步骤别名

有时,为了提高可读性,必须用不同的名称声明相同的fixture或步骤。要使用具有多个步骤名称的相同步骤函数,只需多次装饰它即可。

@given("I have an article")
@given("there's an article")
def article(author, target_fixture="article"):
    return create_test_article(author=author)

请注意,给定的步骤别名是独立的,当提到它们时会执行。

例如,如果您将资源关联到某些所有者或未关联。管理员用户不能是文章的作者,但文章应该有一个默认作者。

Feature: Resource owner
    Scenario: I'm the author
        Given I'm an author
        And I have an article


    Scenario: I'm the admin
        Given I'm the admin
        And there's an article

步骤参数

通常可以重用步骤,给它们提供参数。这允许有单个实现和多个使用,从而减少代码。还打开了在单个场景中两次使用相同的步骤并使用不同参数的可能性!更重要的是,还有几种类型的步骤参数解析器可供您使用(灵感来自behave实现)

字符串(默认)

这是默认的,可以认为是nullexact解析器。它不解析任何参数,通过字符串的相等性匹配步骤名称。

parse(基于:pypi_parse

提供了一种简单的解析器,用可读的语法替换步骤参数的正则表达式,如{param:Type}。语法灵感来自Python内置的string.format()函数。步骤参数必须使用pypi_parse的命名字段语法在步骤定义中使用。命名字段被提取,可选地进行类型转换,然后作为步骤函数的参数使用。支持使用通过extra_types传递的类型转换器进行类型转换。

cfparse(扩展:pypi_parse,基于:pypi_parse_type

提供了一种扩展解析器,支持“基数字段”(CF)功能。只要提供了基数=1的类型转换器,就会自动为相关基数创建缺失的类型转换器。支持如下的解析表达式:* {values:Type+}(基数=1..N,多个)* {values:Type*}(基数=0..N,多个0)* {value:Type?}(基数=0..1,可选)支持类型转换(如上所述)。

re

这使用完整的正则表达式来解析子句文本。您需要使用命名组“(?P...)”来定义从文本中拉取的变量,并将其传递到您的step()函数中。类型转换只能通过converters步骤装饰器参数来完成(见下例)。

默认解析器是 string,所以只是对关键词定义进行一对一匹配。除了 string 以外的解析器及其可选参数的指定如下:

对于 cfparse 解析器

from pytest_bdd import parsers

@given(
    parsers.cfparse("there are {start:Number} cucumbers", extra_types={"Number": int}),
    target_fixture="cucumbers",
)
def given_cucumbers(start):
    return {"start": start, "eat": 0}

对于 re 解析器

from pytest_bdd import parsers

@given(
    parsers.re(r"there are (?P<start>\d+) cucumbers"),
    converters={"start": int},
    target_fixture="cucumbers",
)
def given_cucumbers(start):
    return {"start": start, "eat": 0}

示例

Feature: Step arguments
    Scenario: Arguments for given, when, then
        Given there are 5 cucumbers

        When I eat 3 cucumbers
        And I eat 2 cucumbers

        Then I should have 0 cucumbers

代码将如下所示:

from pytest_bdd import scenarios, given, when, then, parsers


scenarios("arguments.feature")


@given(parsers.parse("there are {start:d} cucumbers"), target_fixture="cucumbers")
def given_cucumbers(start):
    return {"start": start, "eat": 0}


@when(parsers.parse("I eat {eat:d} cucumbers"))
def eat_cucumbers(cucumbers, eat):
    cucumbers["eat"] += eat


@then(parsers.parse("I should have {left:d} cucumbers"))
def should_have_left_cucumbers(cucumbers, left):
    assert cucumbers["start"] - cucumbers["eat"] == left

示例代码还展示了传递参数转换器的可能性,如果需要在解析器之后对步骤参数进行后处理,这可能很有用。

您可以实现自己的步骤解析器。其接口非常简单。代码可能如下所示:

import re
from pytest_bdd import given, parsers


class MyParser(parsers.StepParser):
    """Custom parser."""

    def __init__(self, name, **kwargs):
        """Compile regex."""
        super().__init__(name)
        self.regex = re.compile(re.sub("%(.+)%", "(?P<\1>.+)", self.name), **kwargs)

    def parse_arguments(self, name):
        """Get step arguments.

        :return: `dict` of step arguments
        """
        return self.regex.match(name).groupdict()

    def is_matching(self, name):
        """Match given name with the step name."""
        return bool(self.regex.match(name))


@given(parsers.parse("there are %start% cucumbers"), target_fixture="cucumbers")
def given_cucumbers(start):
    return {"start": start, "eat": 0}

通过给定的步骤覆盖固定值

如果您的测试设置数据结构复杂,依赖注入并不是万能的。有时需要这样的步骤,它将强制仅针对某些测试(场景)更改固定值,而对于其他测试则保持不变。为了允许这样做,given 装饰器中存在特殊的 target_fixture 参数。

from pytest_bdd import given

@pytest.fixture
def foo():
    return "foo"


@given("I have injecting given", target_fixture="foo")
def injecting_given():
    return "injected foo"


@then('foo should be "injected foo"')
def foo_is_foo(foo):
    assert foo == 'injected foo'
Feature: Target fixture
    Scenario: Test given fixture injection
        Given I have injecting given
        Then foo should be "injected foo"

在这个例子中,现有的固定值 foo 将仅在其使用的场景中被给定的步骤 I have injecting given 覆盖。

有时让 whenthen 步骤也提供固定值也是有用的。一个常见的用例是当我们必须断言 HTTP 请求的结果时。

# content of test_blog.py

from pytest_bdd import scenarios, given, when, then

from my_app.models import Article

scenarios("blog.feature")


@given("there is an article", target_fixture="article")
def there_is_an_article():
    return Article()


@when("I request the deletion of the article", target_fixture="request_result")
def there_should_be_a_new_article(article, http_client):
    return http_client.delete(f"/articles/{article.uid}")


@then("the request should be successful")
def article_is_published(request_result):
    assert request_result.status_code == 200
# content of blog.feature

Feature: Blog
    Scenario: Deleting the article
        Given there is an article

        When I request the deletion of the article

        Then the request should be successful

多行步骤

与 Gherkin 一样,pytest-bdd 支持多行步骤(也称为 文档字符串)。但以一种更干净、更强大的方式。

Feature: Multiline steps
    Scenario: Multiline step using sub indentation
        Given I have a step with:
            Some
            Extra
            Lines
        Then the text should be parsed with correct indentation

如果一个步骤的第一行之后的下一行(或多行)相对于第一行缩进,则该步骤被视为多行步骤。然后通过添加带有换行符的额外行简单地扩展步骤名称。在上面的例子中,Given 步骤名称将是:

'I have a step with:\nSome\nExtra\nLines'

当然,您可以使用包括换行符在内的完整名称注册一个步骤,但使用步骤参数并在第一行之后捕获(或它们的子集)行到参数中似乎更实用。

from pytest_bdd import given, then, scenario, parsers


scenarios("multiline.feature")


@given(parsers.parse("I have a step with:\n{content}"), target_fixture="text")
def given_text(content):
    return content


@then("the text should be parsed with correct indentation")
def text_should_be_correct(text):
    assert text == "Some\nExtra\nLines"

场景快捷方式

如果您有一组相对较大的功能文件,手动使用场景装饰器绑定场景会很无聊。当然,手动方法使您能够获得额外的能力,例如对测试进行参数化、为测试函数提供一个好的名称、对其进行文档化等,但在大多数情况下,您不需要这些。相反,您希望使用 scenarios 辅助程序自动递归地将 features 文件夹(夹)中找到的所有场景绑定。

from pytest_bdd import scenarios

# assume 'features' subfolder is in this file's directory
scenarios('features')

这就是您需要做的,以绑定 features 文件夹中找到的所有场景!请注意,您可以传递多个路径,这些路径可以是功能文件或功能文件夹。

from pytest_bdd import scenarios

# pass multiple paths/files
scenarios('features', 'other_features/some.feature', 'some_other_features')

但是,如果您需要手动绑定某个场景,而让其他场景自动绑定怎么办?只需像“常规”方式一样编写您的场景,但确保您在调用 scenarios 辅助程序之前这样做。

from pytest_bdd import scenario, scenarios

@scenario('features/some.feature', 'Test something')
def test_something():
    pass

# assume 'features' subfolder is in this file's directory
scenarios('features')

在上面的例子中,test_something 场景绑定将保持手动,features 文件夹中找到的其他场景将自动绑定。

场景概述

场景可以被参数化以涵盖多个情况。在 Gherkin 中,这些被称为 场景概述,变量模板使用尖括号编写(例如,<var_name>)。

示例

# content of scenario_outlines.feature

Feature: Scenario outlines
    Scenario Outline: Outlined given, when, then
        Given there are <start> cucumbers
        When I eat <eat> cucumbers
        Then I should have <left> cucumbers

        Examples:
        | start | eat | left |
        |  12   |  5  |  7   |
from pytest_bdd import scenarios, given, when, then, parsers


scenarios("scenario_outlines.feature")


@given(parsers.parse("there are {start:d} cucumbers"), target_fixture="cucumbers")
def given_cucumbers(start):
    return {"start": start, "eat": 0}


@when(parsers.parse("I eat {eat:d} cucumbers"))
def eat_cucumbers(cucumbers, eat):
    cucumbers["eat"] += eat


@then(parsers.parse("I should have {left:d} cucumbers"))
def should_have_left_cucumbers(cucumbers, left):
    assert cucumbers["start"] - cucumbers["eat"] == left

组织您的场景

您拥有的功能和场景越多,它们组织的问题就越重要。您可以做的事情(这也是推荐的方式)

  • 按语义组组织您的功能文件文件夹

features
│
├──frontend
│  │
│  └──auth
│     │
│     └──login.feature
└──backend
   │
   └──auth
      │
      └──login.feature

看起来没问题,但如何仅运行特定功能的测试呢?因为pytest-bdd使用pytest,而BDD场景实际上是普通测试。但是测试文件与功能文件是分开的,映射由开发者决定,因此测试文件的结构可能看起来完全不同。

tests
│
└──functional
   │
   └──test_auth.py
      │
      └ """Authentication tests."""
        from pytest_bdd import scenario

        @scenario('frontend/auth/login.feature')
        def test_logging_in_frontend():
            pass

        @scenario('backend/auth/login.feature')
        def test_logging_in_backend():
            pass

为了选择要运行的测试,我们可以使用测试选择技术。问题是您必须知道测试是如何组织的,仅知道功能文件的组织是不够的。Cucumber使用标签作为分类功能和场景的方式,pytest-bdd支持这一点。例如,我们可以有

@login @backend
Feature: Login

  @successful
  Scenario: Successful login

pytest-bdd使用pytest标记作为给定场景测试标签的存储,因此我们可以使用标准的测试选择。

pytest -m "backend and login and successful"

功能和场景标记与标准pytest标记没有区别,并且@符号会自动去除,以便允许测试选择表达式。如果您想使BDD相关的标签与其他测试标记区分开来,请使用前缀如bdd。注意,如果您使用带有--strict选项的pytest,则特征文件中提到的所有BDD标签也应包含在pytest.ini配置的markers设置中。此外,对于标签,请使用与Python兼容的变量名称,即以非数字开头,只包含下划线或字母数字字符等。这样,您可以安全地使用标签进行测试筛选。

您可以通过实现pytest_bdd_apply_tag钩子来自定义如何将标签转换为pytest标记,并从它返回True

def pytest_bdd_apply_tag(tag, function):
    if tag == 'todo':
        marker = pytest.mark.skip(reason="Not implemented yet")
        marker(function)
        return True
    else:
        # Fall back to the default behavior of pytest-bdd
        return None

测试设置

测试设置在Given部分中实现。尽管这些步骤以命令方式执行以应用可能的副作用,但pytest-bdd试图利用基于依赖注入的PyTest fixtures,这使设置更具声明性风格。

@given("I have a beautiful article", target_fixture="article")
def article():
    return Article(is_beautiful=True)

目标PyTest fixture“article”获取返回值,任何其他步骤都可以依赖于它。

Feature: The power of PyTest
    Scenario: Symbolic name across steps
        Given I have a beautiful article
        When I publish this article

When步骤引用article来发布它。

@when("I publish this article")
def publish_article(article):
    article.publish()

许多其他BDD工具包在全局上下文中操作,并将副作用放在那里。这使得实现步骤非常困难,因为依赖关系只在运行时作为副作用出现,而不是在代码中声明。“发布文章”步骤必须信任文章已经在上下文中,必须知道它存储的属性的名称、类型等。

在pytest-bdd中,您只需声明步骤函数的依赖参数即可,PyTest将确保提供它。

尽管如此,仍然可以通过BDD的设计以命令方式应用副作用。

Feature: News website
    Scenario: Publishing an article
        Given I have a beautiful article
        And my article is published

功能测试可以重用为单元测试创建的fixture库,并通过应用副作用来升级它们。

@pytest.fixture
def article():
    return Article(is_beautiful=True)


@given("I have a beautiful article")
def i_have_a_beautiful_article(article):
    pass


@given("my article is published")
def published_article(article):
    article.publish()
    return article

这样,副作用已应用于我们的文章,PyTest确保所有需要“article”fixture的步骤都将收到相同的对象。 “published_article”和“article”fixture的值是相同的对象。

Fixtures在PyTest范围内只评估一次,并且它们的值被缓存。

背景

通常情况下,为了覆盖某些功能,您可能需要多个场景。而且,这些场景的设置有一些共同的部分(如果不完全相同)是合理的。为此,有背景。pytest-bdd为功能实现了Gherkin背景

Feature: Multiple site support

  Background:
    Given a global administrator named "Greg"
    And a blog named "Greg's anti-tax rants"
    And a customer named "Wilson"
    And a blog named "Expensive Therapy" owned by "Wilson"

  Scenario: Wilson posts to his own blog
    Given I am logged in as Wilson
    When I try to post to "Expensive Therapy"
    Then I should see "Your article was published."

  Scenario: Greg posts to a client's blog
    Given I am logged in as Greg
    When I try to post to "Expensive Therapy"
    Then I should see "Your article was published."

在这个示例中,所有背景步骤将在场景本身的所有给定步骤之前执行,这为在单个特性中为多个场景准备一些公共设置提供了可能性。有关背景的最佳实践,请阅读 Gherkin 的使用背景的提示

复用固定装置

有时场景会为可以继承(复用)的现有固定装置定义新的名称。例如,如果我们有 pytest 固定装置

@pytest.fixture
def article():
   """Test article."""
   return Article()

然后这个固定装置可以用其他名称通过 given() 进行复用

@given('I have a beautiful article')
def i_have_an_article(article):
   """I have an article."""

复用步骤

可以在父 conftest.py 中定义一些公共步骤,并在子测试文件中简单地期望它们。

# content of common_steps.feature

Scenario: All steps are declared in the conftest
    Given I have a bar
    Then bar should have value "bar"
# content of conftest.py

from pytest_bdd import given, then


@given("I have a bar", target_fixture="bar")
def bar():
    return "bar"


@then('bar should have value "bar"')
def bar_is_bar(bar):
    assert bar == "bar"
# content of test_common.py

@scenario("common_steps.feature", "All steps are declared in the conftest")
def test_conftest():
    pass

测试文件中没有步骤的定义。它们是从父 conftest.py 收集的。

默认步骤

以下是 pytest-bdd 内部实现的步骤列表

given
  • trace - 通过 pytest.set_trace() 进入 pdb 调试器

when
  • trace - 通过 pytest.set_trace() 进入 pdb 调试器

then
  • trace - 通过 pytest.set_trace() 进入 pdb 调试器

特性文件路径

默认情况下,pytest-bdd 将使用当前模块的路径作为查找特性文件的基准路径,但此行为可以在 pytest 配置文件(例如 pytest.initox.inisetup.cfg)中通过在 bdd_features_base_dir 键中声明新基准路径来更改。路径被解释为相对于 pytest 根目录 的相对路径。您也可以按场景覆盖特性基准路径,以覆盖特定测试的路径。

pytest.ini

[pytest]
bdd_features_base_dir = features/

tests/test_publish_article.py

from pytest_bdd import scenario


@scenario("foo.feature", "Foo feature in features/foo.feature")
def test_foo():
    pass


@scenario(
    "foo.feature",
    "Foo feature in tests/local-features/foo.feature",
    features_base_dir="./local-features/",
)
def test_foo_local():
    pass

也可以将 features_base_dir 参数传递给 @scenario 装饰器。

避免重复输入特性文件名

如果您想在测试文件中定义场景时避免重复输入特性文件名,请使用 functools.partial。这将使您在测试文件中定义多个场景时更容易。例如

# content of test_publish_article.py

from functools import partial

import pytest_bdd


scenario = partial(pytest_bdd.scenario, "/path/to/publish_article.feature")


@scenario("Publishing the article")
def test_publish():
    pass


@scenario("Publishing the article as unprivileged user")
def test_publish_unprivileged():
    pass

您可以在 Python 文档中了解更多关于 functools.partial 的信息。

程序性步骤生成

有时您有步骤定义,自动生成它们比手动重复编写它们要容易得多。这很常见,例如,当使用像 pytest-factoryboy 这样的库自动创建固定装置时。为每个模型编写步骤定义可能变得很乏味。

因此,pytest-bdd 提供了一种自动生成步骤定义的方法。

技巧是将 stacklevel 参数传递给 givenwhenthenstep 装饰器。这将指示它们在适当的模块中注入步骤固定装置,而不是仅在调用帧中注入它们。

让我们看看一个具体的例子;假设您有一个名为 Wallet 的类,它有每种货币的一些金额

# contents of wallet.py

import dataclass

@dataclass
class Wallet:
    verified: bool

    amount_eur: int
    amount_usd: int
    amount_gbp: int
    amount_jpy: int

您可以使用 pytest-factoryboy 自动为这个类创建模型固定装置

# contents of wallet_factory.py

from wallet import Wallet

import factory
from pytest_factoryboy import register

class WalletFactory(factory.Factory):
    class Meta:
        model = Wallet

    amount_eur = 0
    amount_usd = 0
    amount_gbp = 0
    amount_jpy = 0

register(Wallet)  # creates the "wallet" fixture
register(Wallet, "second_wallet")  # creates the "second_wallet" fixture

现在我们可以定义一个函数 generate_wallet_steps(...),用于创建任何钱包固定步骤(在我们的例子中,它将是 walletsecond_wallet)。

# contents of wallet_steps.py

import re
from dataclasses import fields

import factory
import pytest
from pytest_bdd import given, when, then, scenarios, parsers


def generate_wallet_steps(model_name="wallet", stacklevel=1):
    stacklevel += 1

    human_name = model_name.replace("_", " ")  # "second_wallet" -> "second wallet"

    @given(f"I have a {human_name}", target_fixture=model_name, stacklevel=stacklevel)
    def _(request):
        return request.getfixturevalue(model_name)

    # Generate steps for currency fields:
    for field in fields(Wallet):
        match = re.fullmatch(r"amount_(?P<currency>[a-z]{3})", field.name)
        if not match:
            continue
        currency = match["currency"]

        @given(
            parsers.parse(f"I have {{value:d}} {currency.upper()} in my {human_name}"),
            target_fixture=f"{model_name}__amount_{currency}",
            stacklevel=stacklevel,
        )
        def _(value: int) -> int:
            return value

        @then(
            parsers.parse(f"I should have {{value:d}} {currency.upper()} in my {human_name}"),
            stacklevel=stacklevel,
        )
        def _(value: int, _currency=currency, _model_name=model_name) -> None:
            wallet = request.getfixturevalue(_model_name)
            assert getattr(wallet, f"amount_{_currency}") == value

# Inject the steps into the current module
generate_wallet_steps("wallet")
generate_wallet_steps("second_wallet")

这个最后的文件,wallet_steps.py,现在包含了我们“wallet”和“second_wallet”固定步骤的所有步骤定义。

现在我们可以定义这样的场景

# contents of wallet.feature
Feature: A feature

    Scenario: Wallet EUR amount stays constant
        Given I have 10 EUR in my wallet
        And I have a wallet
        Then I should have 10 EUR in my wallet

    Scenario: Second wallet JPY amount stays constant
        Given I have 100 JPY in my second wallet
        And I have a second wallet
        Then I should have 100 JPY in my second wallet

最后是一个测试文件,它将所有内容整合在一起并运行场景

# contents of test_wallet.py

from pytest_factoryboy import scenarios

from wallet_factory import *  # import the registered fixtures "wallet" and "second_wallet"
from wallet_steps import *  # import all the step definitions into this test file

scenarios("wallet.feature")

钩子

pytest-bdd公开了几个pytest钩子,这些钩子可能有助于在上面构建有用的报告、可视化等。

  • pytest_bdd_before_scenario(request, feature, scenario) - 在执行场景之前调用

  • pytest_bdd_after_scenario(request, feature, scenario) - 在执行场景之后调用(即使其中一个步骤失败了)

  • pytest_bdd_before_step(request, feature, scenario, step, step_func) - 在执行步骤函数之前调用,并评估其参数

  • pytest_bdd_before_step_call(request, feature, scenario, step, step_func, step_func_args) - 在使用评估后的参数执行步骤函数之前调用

  • pytest_bdd_after_step(request, feature, scenario, step, step_func, step_func_args) - 在步骤函数成功执行之后调用

  • pytest_bdd_step_error(request, feature, scenario, step, step_func, step_func_args, exception) - 当步骤函数执行失败时调用

  • pytest_bdd_step_func_lookup_error(request, feature, scenario, step, exception) - 当步骤查找失败时调用

浏览器测试

推荐用于浏览器测试的工具

报告

拥有良好的BDD测试报告非常重要。Cucumber引入了json格式的标准,这可以通过例如这个Jenkins插件来实现。

为了输出json格式的结果

pytest --cucumberjson=<path to json report>

这将输出一个展开的(意味着场景大纲将被展开为多个场景)Cucumber格式。

要在终端上启用gherkin格式的输出,请使用

pytest --gherkin-terminal-reporter

测试代码生成辅助工具

对于新手来说,在没有沮丧的情况下编写所有必要的测试代码有时是很困难的。为了简化他们的生活,实现了一个简单的代码生成器。它允许为给定的特征文件创建完全功能(但当然是空的)的测试和步骤定义。这是由pytest-bdd包提供的独立控制台脚本实现的

pytest-bdd generate <feature file name> .. <feature file nameN>

它将生成的代码打印到标准输出,这样您就可以轻松地将其重定向到文件

pytest-bdd generate features/some.feature > tests/functional/test_some.py

高级代码生成

对于更有经验的用户,有一个智能代码生成/建议功能。它只会生成尚未存在的测试代码,检查现有的测试和步骤定义的方式与测试执行期间的方式相同。代码建议工具通过传递额外的pytest参数调用

pytest --generate-missing --feature features tests/functional

输出将如下所示

============================= test session starts ==============================
platform linux2 -- Python 2.7.6 -- py-1.4.24 -- pytest-2.6.2
plugins: xdist, pep8, cov, cache, bdd, bdd, bdd
collected 2 items

Scenario is not bound to any test: "Code is generated for scenarios which are not bound to any tests" in feature "Missing code generation" in /tmp/pytest-552/testdir/test_generate_missing0/tests/generation.feature
--------------------------------------------------------------------------------

Step is not defined: "I have a custom bar" in scenario: "Code is generated for scenario steps which are not yet defined(implemented)" in feature "Missing code generation" in /tmp/pytest-552/testdir/test_generate_missing0/tests/generation.feature
--------------------------------------------------------------------------------
Please place the code above to the test file(s):

@scenario('tests/generation.feature', 'Code is generated for scenarios which are not bound to any tests')
def test_Code_is_generated_for_scenarios_which_are_not_bound_to_any_tests():
    """Code is generated for scenarios which are not bound to any tests."""


@given("I have a custom bar")
def I_have_a_custom_bar():
    """I have a custom bar."""

作为副作用,该工具将验证文件格式错误,还有一些逻辑错误,例如步骤类型的顺序。

从版本5.x.x迁移测试

pytest-bdd的主要重点是与最新的gherkin发展的兼容性,例如带有标签支持的多个场景大纲示例等。

为了提供最佳兼容性,最好是支持官方gherkin参考中描述的功能。这意味着废弃了pytest-bdd中实现的一些非标准功能。

移除功能示例

在功能级别上的示例表格不再受支持。如果您在功能级别上有示例,请将它们复制到每个单独的场景中。

垂直示例的移除

由于官方Gherkin不支持垂直示例,因此垂直示例表格不再受支持。示例表格应具有水平方向。

步骤参数不再作为固定值

步骤解析参数与固定值冲突。现在它们不再定义固定值。如果必须通过步骤定义固定值,应使用target_fixture参数。

步骤中的变量模板仅在场景概述中解析

在pytest的早期版本中,包含<variable>的步骤将被ScenarioScenario Outline解析。现在它们仅在Scenario Outline内部解析。

从4.x.x版本迁移您的测试

将步骤定义内的<parameter>的用法替换为解析后的{parameter}

模板步骤(例如@given("there are <start> cucumbers"))现在应使用步骤参数解析器以匹配场景概述并从示例表中获取值。示例表中的值不再作为固定值传递,尽管如果您定义步骤以使用解析器,参数仍将作为固定值提供。

# Old step definition:
@given("there are <start> cucumbers")
def given_cucumbers(start):
    pass


# New step definition:
@given(parsers.parse("there are {start} cucumbers"))
def given_cucumbers(start):
    pass

删除场景example_converters以支持步骤级别的转换器

# Old code:
@given("there are <start> cucumbers")
def given_cucumbers(start):
    return {"start": start}

@scenario("outline.feature", "Outlined", example_converters={"start": float})
def test_outline():
    pass


# New code:
@given(parsers.parse("there are {start} cucumbers"), converters={"start": float})
def given_cucumbers(start):
    return {"start": start}

@scenario("outline.feature", "Outlined")
def test_outline():
    pass

拒绝结合场景概述和pytest参数化

结合场景概述和pytest参数化方法的重大缺点是无法从功能文件中看到测试表。

从3.x.x版本迁移您的测试

给定步骤不再作为固定值。如果需要将给定步骤的设置作为固定值,应使用target_fixture参数。

@given("there's an article", target_fixture="article")
def there_is_an_article():
    return Article()

给定步骤不再有fixture参数。实际上,步骤可能依赖于多个固定值。只需使用带有依赖注入的正常步骤声明。

@given("there's an article")
def there_is_an_article(article):
    pass

严格Gherkin选项已删除,因此可以从场景装饰器中删除strict_gherkin参数,以及从ini文件中删除bdd_strict_gherkin

应删除用于钩子pytest_bdd_step_validation_error的步骤验证处理程序。

许可证

本软件根据MIT许可证授权。

© 2013 Oleg Pidsadnyi, Anatoly Bubenkov以及其他人

项目详情


发布历史 发布通知 | RSS源

下载文件

下载适用于您平台的文件。如果您不确定该选择哪个,请了解有关安装包的更多信息。

源分发

pytest_bdd-7.3.0.tar.gz (47.4 kB 查看散列)

上传时间

构建分发

pytest_bdd-7.3.0-py3-none-any.whl (42.2 kB 查看散列)

上传时间 Python 3

由以下机构支持