Pytest的BDD

pytest-bdd的Python项目详细描述


py.test runner的bdd库

http://img.shields.io/pypi/v/pytest-bdd.svghttp://img.shields.io/coveralls/pytest-dev/pytest-bdd/master.svghttps://travis-ci.org/pytest-dev/pytest-bdd.svg?branch=master文档状态

pytest bdd实现了一个子集的gherkin语言以实现自动化项目 需求测试和促进行为驱动的开发。

与许多其他bdd工具不同,它不需要单独的运行器,并且可以从 pytest的威力和灵活性。它使单元和功能统一 测试,减少持续集成服务器配置的负担,并允许 测试设置。

为单元测试编写的pytest fixture可用于设置和操作 在使用依赖注入的功能步骤中提到。这允许一个真正的bdd 在不维护任何上下文对象的情况下对需求进行足够的规范 包含黄瓜命令性声明的副作用。

安装pytest bdd

pip install pytest-bdd

pytest的最低要求版本是3.3.2

示例

博客托管软件的示例测试可以如下所示。 请注意,pytest splinter用于获取浏览器固定设备。

发表文章。功能:

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

Scenario: Publishing the article
    Given I'm an author user
    And I have an articleWhen I go to the article pageAnd I press the publish buttonThen I should not see the error messageAnd the article should be published  # Note: will query the database

请注意,每个功能文件只允许一个功能。

测试发布文章。py:

frompytest_bddimportscenario,given,when,then@scenario('publish_article.feature','Publishing the article')deftest_publish():pass@given("I'm an author user")defauthor_user(auth,author):auth['user']=author.user@given('I have an article')defarticle(author):returncreate_test_article(author=author)@when('I go to the article page')defgo_to_article(article,browser):browser.visit(urljoin(browser.url,'/manage/articles/{0}/'.format(article.id)))@when('I press the publish button')defpublish_article(browser):browser.find_by_css('button[name=publish]').first.click()@then('I should not see the error message')defno_error_message(browser):withpytest.raises(ElementDoesNotExist):browser.find_by_css('.message.error').first@then('the article should be published')defarticle_is_published(article):article.refresh()# Refresh the object in the SQLAlchemy sessionassertarticle.is_published

场景装饰器

Scenario decorator可以接受以下可选关键字参数:

  • 编码 -以特定编码解码特征文件的内容。默认为UTF-8。
  • 示例转换器 -映射到传递函数以转换功能文件中提供的示例值。

场景修饰的函数 decorator的行为类似于一个普通的测试函数, 它们将在所有场景步骤之后执行。 您可以将其视为一个普通的pytest测试函数,例如在那里订购fixture, 调用其他函数并作出断言:

frompytest_bddimportscenario,given,when,then@scenario('publish_article.feature','Publishing the article')deftest_publish(browser):assertarticle.titleinbrowser.html

阶跃别名

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

@given('I have an article')@given('there\'s an article')defarticle(author):returncreate_test_article(author=author)

注意,给定的步骤别名是独立的,将被执行 当被提及时。

例如,是否将资源与某个所有者关联。行政 用户不能是文章的作者,但文章应该具有 默认作者。

Scenario: I'm the author
    Given I'm an authorAnd I have an articleScenario: I'm the admin
    Given I'm the adminAnd there's an article

给定步进范围

如果需要在每个场景中执行给定步骤的次数少于一次(例如:对于模块、会话执行一次),则可以 传递可选的 范围 参数:

@given('there is an article',scope='session')defarticle(author):returncreate_test_article(author=author)
Scenario: I'm the author
    Given I'm an authorAnd there is an articleScenario: I'm the admin
    Given I'm the adminAnd there is an article

在本例中,给定步骤的"there is a article"的step函数将执行一次,即使存在 有两个场景在使用它。 注意,对于其他步骤类型,如果范围大于它们所表示的"函数"(默认值),则没有意义 一个操作(在步骤时)和断言(然后是步骤)。

步骤参数

通常可以重用给它们一个参数的步骤。 这允许有一个实现和多个用途,所以代码更少。 同时也提供了在单一场景中使用同一步骤两次并使用不同参数的可能性! 而且,还有几种类型的step参数解析器供您使用 (想法取自 行为 实现:

< DL>
字符串 (默认值)
这是默认值,可以看作是一个 null exact 解析器。它不解析任何参数 并通过字符串相等来匹配步骤名。
解析 (基于: pypi_parse
提供了一个简单的解析器,用于替换 具有可读语法的步骤参数,如 {param:type} 。 语法的灵感来自于python内置的 string.format() 功能。 步骤参数必须使用命名字段语法 分步定义。将提取命名字段, 可选地转换类型,然后用作步骤函数参数。 通过使用通过附加类型传递的类型转换器支持类型转换
cfparse (扩展: pypi-parse ,基于: pypi-parse-type
提供支持"基数字段"(cf)的扩展解析器。 自动为相关基数创建缺少的类型转换器 只要提供基数为1的类型转换器。 支持如下解析表达式: * {values:type+} (基数=1..n,多) * {值:类型*} (基数=0..n,many0) * {值:类型?} (基数=0..1,可选) 支持类型转换(如上所述)。
re
这使用完整的正则表达式来解析子句文本。你会 需要使用命名组"(?p<;name>;…)定义拉取的变量 从文本传递到 step() 函数。 类型转换只能通过 converters step decorator参数完成(参见下面的示例)。

默认的解析器是 string ,所以只需简单地与关键字定义进行一对一的匹配。 除了字符串之外的解析器及其可选参数被指定如下:

对于 cfparse parser

frompytest_bddimportparsers@given(parsers.cfparse('there are {start:Number} cucumbers',extra_types=dict(Number=int)))defstart_cucumbers(start):returndict(start=start,eat=0)

对于 re 解析器

pip install pytest-bdd
0

示例:

pip install pytest-bdd
1

代码将如下所示:

pip install pytest-bdd
2

示例代码还显示了传递参数转换器的可能性,如果您需要后处理步骤 分析器后的参数。

您可以实现自己的步骤解析器。它的界面很简单。代码可能如下所示:

pip install pytest-bdd
3

步骤参数也是fixture!

步骤参数作为名称等于 论据。这将打开许多可能性:

  • 您可以将step的参数作为fixture在其他step函数中访问,只需将其作为参数(就像其他pytest fixture一样)
  • 如果步骤的名称为argument与现有的fixture冲突,它将被step的参数值覆盖;这样,您就可以通过为step参数选择正确的名称,以特别的方式设置/覆盖fixture树内部某个fixture的值。

通过给定步骤覆盖固定装置

如果测试设置数据的结构很复杂,依赖注入就不是万能的。有时需要 这样一个给定的步骤,它只对特定的测试(场景)强制更改fixture,而对其他测试则强制更改fixture 它将保持原样。为了实现这一点,特定的参数 target_fixture 存在于给定的 装饰器中:

pip install pytest-bdd
4
pip install pytest-bdd
5

在这个例子中,现有的fixture foo 将被给定的步骤覆盖,我已经注入了给定的 用于:< > >

多行步骤

作为gherkin,pytest bdd支持多行步骤 (又名 pystrings )。 但更干净和强大的方式:

pip install pytest-bdd
6

如果第一行之后的下一行相对缩进,则步骤被视为多行步骤 到第一行。然后,只需通过添加更多带有换行符的行来扩展步骤名。 在上面的示例中,给定的步骤名称为:

pip install pytest-bdd
7

当然可以使用全名(包括换行符)注册步骤,但是使用 将参数和捕获行放在参数的第一行(或其某些子集)之后:

pip install pytest-bdd
8

注意,在本例中, 然后 步骤定义( 文本应该是正确的 )使用提供的 文本 fixture 通过给定的步骤( i_have_text )参数使用相同的名称( text )。这种可能性在 步骤参数也是fixture!节。

方案快捷方式

如果您有一组相对较大的功能文件,那么使用 场景装饰器。当然,使用手动方法,您可以获得所有的权力,以便能够额外的参数化 测试,给测试函数一个好名字,记录它,等等,但是在大多数情况下你不需要这样做。 相反,您希望自动递归地绑定 功能中的所有场景。 为此-有一个 场景 助手。

pip install pytest-bdd
9

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

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

Scenario: Publishing the article
    Given I'm an author user
    And I have an articleWhen I go to the article pageAnd I press the publish buttonThen I should not see the error messageAnd the article should be published  # Note: will query the database
0

但是如果您需要手动绑定某些场景,而让其他场景自动绑定呢? 只需以一种正常的方式编写场景,但要确保在调用场景助手之前完成。

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

Scenario: Publishing the article
    Given I'm an author user
    And I have an articleWhen I go to the article pageAnd I press the publish buttonThen I should not see the error messageAnd the article should be published  # Note: will query the database
1

在上面的例子中, test_一些 场景绑定将保持手动,其他场景可以在 功能中找到。 文件夹将自动绑定。

情景概述

场景可以参数化以覆盖少数情况。在变量的小黄瓜里 模板使用角括号作为<;somevalue>;编写。 pytest bdd支持小黄瓜场景大纲。 正如be behave中所描述的那样。 文档。

示例:

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

Scenario: Publishing the article
    Given I'm an author user
    And I have an articleWhen I go to the article pageAnd I press the publish buttonThen I should not see the error messageAnd the article should be published  # Note: will query the database
2

Pytest BDD功能文件格式还以不同的方式支持示例表:

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

Scenario: Publishing the article
    Given I'm an author user
    And I have an articleWhen I go to the article pageAnd I press the publish buttonThen I should not see the error messageAnd the article should be published  # Note: will query the database
3

此窗体允许具有许多列的表保留可预测的最大文本宽度 可读性更改。

代码将如下所示:

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

Scenario: Publishing the article
    Given I'm an author user
    And I have an articleWhen I go to the article pageAnd I press the publish buttonThen I should not see the error messageAnd the article should be published  # Note: will query the database
4

示例代码还显示了传递示例转换器的可能性,如果需要参数类型,这可能很有用 不同于字符串。

功能示例

可以为整个特性声明一次示例表,它将被共享 在该功能的所有场景中:

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

Scenario: Publishing the article
    Given I'm an author user
    And I have an articleWhen I go to the article pageAnd I press the publish buttonThen I should not see the error messageAnd the article should be published  # Note: will query the database
5

对于一些更复杂的情况,您可能需要在两个级别上进行参数化:功能和场景。 只要参数名不冲突,就允许这样做:

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

Scenario: Publishing the article
    Given I'm an author user
    And I have an articleWhen I go to the article pageAnd I press the publish buttonThen I should not see the error messageAnd the article should be published  # Note: will query the database
6

结合场景大纲和pytest参数化

也可以在python端参数化场景。 原因是,有时不需要为每个场景都提到示例表。

代码将如下所示:

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

Scenario: Publishing the article
    Given I'm an author user
    And I have an articleWhen I go to the article pageAnd I press the publish buttonThen I should not see the error messageAnd the article should be published  # Note: will query the database
7

使用参数化的.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 articleWhen I go to the article pageAnd I press the publish buttonThen I should not see the error messageAnd the article should be published  # Note: will query the database
8

这种方法的显著缺点是无法从功能文件中看到测试表。

组织场景

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

  • 按语义组组织文件夹中的功能文件:
Feature: Blog
    A site where you can publish your articles.

Scenario: Publishing the article
    Given I'm an author user
    And I have an articleWhen I go to the article pageAnd I press the publish buttonThen I should not see the error messageAnd the article should be published  # Note: will query the database
9

这看起来不错,但是如何只对特定功能运行测试呢? 因为pytest bdd使用pytest,而bdd场景实际上是普通的测试。但是测试文件 独立于功能文件,映射由开发人员决定,因此测试文件结构可以 完全不同:

frompytest_bddimportscenario,given,when,then@scenario('publish_article.feature','Publishing the article')deftest_publish():pass@given("I'm an author user")defauthor_user(auth,author):auth['user']=author.user@given('I have an article')defarticle(author):returncreate_test_article(author=author)@when('I go to the article page')defgo_to_article(article,browser):browser.visit(urljoin(browser.url,'/manage/articles/{0}/'.format(article.id)))@when('I press the publish button')defpublish_article(browser):browser.find_by_css('button[name=publish]').first.click()@then('I should not see the error message')defno_error_message(browser):withpytest.raises(ElementDoesNotExist):browser.find_by_css('.message.error').first@then('the article should be published')defarticle_is_published(article):article.refresh()# Refresh the object in the SQLAlchemy sessionassertarticle.is_published
0

我们可以使用 测试选择技术。问题是 你必须知道你的测试是如何组织的,仅仅知道功能文件组织是不够的。 黄瓜标签 介绍对功能进行分类的标准方法 以及pytest bdd支持的场景。例如,我们可以有:

frompytest_bddimportscenario,given,when,then@scenario('publish_article.feature','Publishing the article')deftest_publish():pass@given("I'm an author user")defauthor_user(auth,author):auth['user']=author.user@given('I have an article')defarticle(author):returncreate_test_article(author=author)@when('I go to the article page')defgo_to_article(article,browser):browser.visit(urljoin(browser.url,'/manage/articles/{0}/'.format(article.id)))@when('I press the publish button')defpublish_article(browser):browser.find_by_css('button[name=publish]').first.click()@then('I should not see the error message')defno_error_message(browser):withpytest.raises(ElementDoesNotExist):browser.find_by_css('.message.error').first@then('the article should be published')defarticle_is_published(article):article.refresh()# Refresh the object in the SQLAlchemy sessionassertarticle.is_published
1

Pytest BDD使用Pytest标记作为给定标记的存储 场景测试,因此我们可以使用标准测试选择:

frompytest_bddimportscenario,given,when,then@scenario('publish_article.feature','Publishing the article')deftest_publish():pass@given("I'm an author user")defauthor_user(auth,author):auth['user']=author.user@given('I have an article')defarticle(author):returncreate_test_article(author=author)@when('I go to the article page')defgo_to_article(article,browser):browser.visit(urljoin(browser.url,'/manage/articles/{0}/'.format(article.id)))@when('I press the publish button')defpublish_article(browser):browser.find_by_css('button[name=publish]').first.click()@then('I should not see the error message')defno_error_message(browser):withpytest.raises(ElementDoesNotExist):browser.find_by_css('.message.error').first@then('the article should be published')defarticle_is_published(article):article.refresh()# Refresh the object in the SQLAlchemy sessionassertarticle.is_published
2

特性和场景标记与标准pytest标记没有区别,并且去掉了 @ 符号 自动允许测试选择器表达式。如果您希望与bdd相关的标记与 其他测试标记,使用前缀,如 bdd 。 注意,如果使用pytest –strict 选项,则功能文件中提到的所有bdd标记也应位于 标记设置。对于标记,请使用python comparitable变量的名称 名称,例如以非数字、下划线、字母数字等开头,这样您就可以安全地使用标记进行测试筛选。

通过实现 pytest_bdd_apply_tag 钩子并从中返回 true

frompytest_bddimportscenario,given,when,then@scenario('publish_article.feature','Publishing the article')deftest_publish():pass@given("I'm an author user")defauthor_user(auth,author):auth['user']=author.user@given('I have an article')defarticle(author):returncreate_test_article(author=author)@when('I go to the article page')defgo_to_article(article,browser):browser.visit(urljoin(browser.url,'/manage/articles/{0}/'.format(article.id)))@when('I press the publish button')defpublish_article(browser):browser.find_by_css('button[name=publish]').first.click()@then('I should not see the error message')defno_error_message(browser):withpytest.raises(ElementDoesNotExist):browser.find_by_css('.message.error').first@then('the article should be published')defarticle_is_published(article):article.refresh()# Refresh the object in the SQLAlchemy sessionassertarticle.is_published
3

测试设置

测试设置在给定的部分中实现。即使这些步骤 为了应用可能的副作用,pytest bdd正在尝试 利用基于依赖注入的pytest fixture 使设置更具声明性。

frompytest_bddimportscenario,given,when,then@scenario('publish_article.feature','Publishing the article')deftest_publish():pass@given("I'm an author user")defauthor_user(auth,author):auth['user']=author.user@given('I have an article')defarticle(author):returncreate_test_article(author=author)@when('I go to the article page')defgo_to_article(article,browser):browser.visit(urljoin(browser.url,'/manage/articles/{0}/'.format(article.id)))@when('I press the publish button')defpublish_article(browser):browser.find_by_css('button[name=publish]').first.click()@then('I should not see the error message')defno_error_message(browser):withpytest.raises(ElementDoesNotExist):browser.find_by_css('.message.error').first@then('the article should be published')defarticle_is_published(article):article.refresh()# Refresh the object in the SQLAlchemy sessionassertarticle.is_published
4

这还声明了pytest fixture"article",任何其他步骤都可以依赖它。

frompytest_bddimportscenario,given,when,then@scenario('publish_article.feature','Publishing the article')deftest_publish():pass@given("I'm an author user")defauthor_user(auth,author):auth['user']=author.user@given('I have an article')defarticle(author):returncreate_test_article(author=author)@when('I go to the article page')defgo_to_article(article,browser):browser.visit(urljoin(browser.url,'/manage/articles/{0}/'.format(article.id)))@when('I press the publish button')defpublish_article(browser):browser.find_by_css('button[name=publish]').first.click()@then('I should not see the error message')defno_error_message(browser):withpytest.raises(ElementDoesNotExist):browser.find_by_css('.message.error').first@then('the article should be published')defarticle_is_published(article):article.refresh()# Refresh the object in the SQLAlchemy sessionassertarticle.is_published
5

当step引用文章来发布它时。

frompytest_bddimportscenario,given,when,then@scenario('publish_article.feature','Publishing the article')deftest_publish():pass@given("I'm an author user")defauthor_user(auth,author):auth['user']=author.user@given('I have an article')defarticle(author):returncreate_test_article(author=author)@when('I go to the article page')defgo_to_article(article,browser):browser.visit(urljoin(browser.url,'/manage/articles/{0}/'.format(article.id)))@when('I press the publish button')defpublish_article(browser):browser.find_by_css('button[name=publish]').first.click()@then('I should not see the error message')defno_error_message(browser):withpytest.raises(ElementDoesNotExist):browser.find_by_css('.message.error').first@then('the article should be published')defarticle_is_published(article):article.refresh()# Refresh the object in the SQLAlchemy sessionassertarticle.is_published
6

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

在pytest bdd中,您只需声明它所依赖的step函数的一个参数 pytest将确保提供它。

静物副作用可以通过bdd的设计应用于命令式风格。

frompytest_bddimportscenario,given,when,then@scenario('publish_article.feature','Publishing the article')deftest_publish():pass@given("I'm an author user")defauthor_user(auth,author):auth['user']=author.user@given('I have an article')defarticle(author):returncreate_test_article(author=author)@when('I go to the article page')defgo_to_article(article,browser):browser.visit(urljoin(browser.url,'/manage/articles/{0}/'.format(article.id)))@when('I press the publish button')defpublish_article(browser):browser.find_by_css('button[name=publish]').first.click()@then('I should not see the error message')defno_error_message(browser):withpytest.raises(ElementDoesNotExist):browser.find_by_css('.message.error').first@then('the article should be published')defarticle_is_published(article):article.refresh()# Refresh the object in the SQLAlchemy sessionassertarticle.is_published
7

功能测试可以重用为单元测试和升级创建的fixture库 通过应用副作用来实现。

frompytest_bddimportscenario,given,when,then@scenario('publish_article.feature','Publishing the article')deftest_publish():pass@given("I'm an author user")defauthor_user(auth,author):auth['user']=author.user@given('I have an article')defarticle(author):returncreate_test_article(author=author)@when('I go to the article page')defgo_to_article(article,browser):browser.visit(urljoin(browser.url,'/manage/articles/{0}/'.format(article.id)))@when('I press the publish button')defpublish_article(browser):browser.find_by_css('button[name=publish]').first.click()@then('I should not see the error message')defno_error_message(browser):withpytest.raises(ElementDoesNotExist):browser.find_by_css('.message.error').first@then('the article should be published')defarticle_is_published(article):article.refresh()# Refresh the object in the SQLAlchemy sessionassertarticle.is_published
8

这样,副作用就应用到了我们的文章中,pytest确保了 需要"article"fixture的步骤将接收相同的对象。价值 "已发表文章"和"文章"固定装置的对象相同。

在pytest作用域中,fixture只计算一次,并且缓存它们的值。 如果给定步骤和提到相同步骤的步骤参数 没有意义。它不会被第二次执行。

frompytest_bddimportscenario,given,when,then@scenario('publish_article.feature','Publishing the article')deftest_publish():pass@given("I'm an author user")defauthor_user(auth,author):auth['user']=author.user@given('I have an article')defarticle(author):returncreate_test_article(author=author)@when('I go to the article page')defgo_to_article(article,browser):browser.visit(urljoin(browser.url,'/manage/articles/{0}/'.format(article.id)))@when('I press the publish button')defpublish_article(browser):browser.find_by_css('button[name=publish]').first.click()@then('I should not see the error message')defno_error_message(browser):withpytest.raises(ElementDoesNotExist):browser.find_by_css('.message.error').first@then('the article should be published')defarticle_is_published(article):article.refresh()# Refresh the object in the SQLAlchemy sessionassertarticle.is_published
9

即使在使用正则表达式的步骤中,pytest bdd也会引发异常 获取参数的模式。

frompytest_bddimportscenario,given,when,then@scenario('publish_article.feature','Publishing the article')deftest_publish(browser):assertarticle.titleinbrowser.html
0

如果步骤使用正则表达式模式,将引发异常。

frompytest_bddimportscenario,given,when,then@scenario('publish_article.feature','Publishing the article')deftest_publish(browser):assertarticle.titleinbrowser.html
1

背景

通常情况下,要涵盖某些功能,需要多个场景。合乎逻辑的是 这些场景的设置将有一些公共部分(如果不相等)。为此,有背景。 Pytest BDD为 功能。

frompytest_bddimportscenario,given,when,then@scenario('publish_article.feature','Publishing the article')deftest_publish(browser):assertarticle.titleinbrowser.html
2

在本例中,所有后台步骤都将在场景自身给定的所有步骤之前执行 步骤,添加在单个功能中为多个场景准备一些通用设置的可能性。 关于背景最佳实践,请阅读 此处

< div > 注

"背景"部分只应使用"给定"步骤, 禁止使用"when"和"then"步骤,因为它们的目的是 与行动和消费结果相关,即 "背景"目标-准备测试系统或"放置系统 在一个已知的状态下"作为"给予它。 上面的语句适用于严格的gherkin模式,即 默认启用。

欢迎加入QQ群-->: 979659372 Python中文网_新手群

推荐PyPI第三方库


热门话题
JavaFX进度条从单独的函数更改而来   jvm使用java服务器选项   java在<li>元素中查找同名的最后一个链接   java问题将参数传递给不同公共类中的构造函数   如何在php中从java函数中获取字符串   java如何在Android中动态显示多个tile   java仅使用Ribbon而不使用任何服务注册表是否可以实现负载平衡?   Jersey 1.19版本的java Swagger JAXRS出现“冲突URI模板”错误   带H2数据库的java Spring boot jpa   从12:00:00到00:00:00的日期转换   Android中的java如何设置文本?   java密钥库“不支持的保护参数”   http使用Java在Java中发送httprequest。净包   SpringJava刷新数据库   java在Spring Boot应用程序中使用嵌入式MongoDb和MongoTemplate失败   java需要什么MatOfMatch对象?   xml使用Java中的合并算法将两个值合并为单个值   java SQLite数据库不保存数据为什么不工作