Pytest的BDD
pytest-bdd的Python项目详细描述
py.test runner的bdd库
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>默认的解析器是 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-bdd0
示例:
pip install pytest-bdd1
代码将如下所示:
pip install pytest-bdd2
示例代码还显示了传递参数转换器的可能性,如果您需要后处理步骤 分析器后的参数。
您可以实现自己的步骤解析器。它的界面很简单。代码可能如下所示:
pip install pytest-bdd3
步骤参数也是fixture!
步骤参数作为名称等于 论据。这将打开许多可能性:
- 您可以将step的参数作为fixture在其他step函数中访问,只需将其作为参数(就像其他pytest fixture一样)
- 如果步骤的名称为argument与现有的fixture冲突,它将被step的参数值覆盖;这样,您就可以通过为step参数选择正确的名称,以特别的方式设置/覆盖fixture树内部某个fixture的值。
通过给定步骤覆盖固定装置
如果测试设置数据的结构很复杂,依赖注入就不是万能的。有时需要 这样一个给定的步骤,它只对特定的测试(场景)强制更改fixture,而对其他测试则强制更改fixture 它将保持原样。为了实现这一点,特定的参数 target_fixture 存在于给定的 装饰器中:
pip install pytest-bdd4
pip install pytest-bdd5
在这个例子中,现有的fixture foo 将被给定的步骤覆盖,我已经注入了给定的 用于:< > >
多行步骤
作为gherkin,pytest bdd支持多行步骤 (又名 pystrings )。 但更干净和强大的方式:
pip install pytest-bdd6
如果第一行之后的下一行相对缩进,则步骤被视为多行步骤 到第一行。然后,只需通过添加更多带有换行符的行来扩展步骤名。 在上面的示例中,给定的步骤名称为:
pip install pytest-bdd7
当然可以使用全名(包括换行符)注册步骤,但是使用 将参数和捕获行放在参数的第一行(或其某些子集)之后:
pip install pytest-bdd8
注意,在本例中, 然后 步骤定义( 文本应该是正确的 )使用提供的 文本 fixture 通过给定的步骤( i_have_text )参数使用相同的名称( text )。这种可能性在 步骤参数也是fixture!节。
方案快捷方式
如果您有一组相对较大的功能文件,那么使用 场景装饰器。当然,使用手动方法,您可以获得所有的权力,以便能够额外的参数化 测试,给测试函数一个好名字,记录它,等等,但是在大多数情况下你不需要这样做。 相反,您希望自动递归地绑定 功能中的所有场景。 为此-有一个 场景 助手。
pip install pytest-bdd9
这就是绑定 功能 文件夹中的所有场景所需做的全部工作! 请注意,您可以传递多个路径,这些路径可以是功能文件或功能文件夹。
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 database0
但是如果您需要手动绑定某些场景,而让其他场景自动绑定呢? 只需以一种正常的方式编写场景,但要确保在调用场景助手之前完成。
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 database1
在上面的例子中, 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 database2
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 database3
此窗体允许具有许多列的表保留可预测的最大文本宽度 可读性更改。
代码将如下所示:
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 database4
示例代码还显示了传递示例转换器的可能性,如果需要参数类型,这可能很有用 不同于字符串。
功能示例
可以为整个特性声明一次示例表,它将被共享 在该功能的所有场景中:
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 database5
对于一些更复杂的情况,您可能需要在两个级别上进行参数化:功能和场景。 只要参数名不冲突,就允许这样做:
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 database6
结合场景大纲和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 database7
使用参数化的.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 database8
这种方法的显著缺点是无法从功能文件中看到测试表。
组织场景
您拥有的功能和场景越多,组织的问题就越重要。 您可以做的事情(这也是推荐的方法):
- 按语义组组织文件夹中的功能文件:
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 database9
这看起来不错,但是如何只对特定功能运行测试呢? 因为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_published0
我们可以使用 测试选择技术。问题是 你必须知道你的测试是如何组织的,仅仅知道功能文件组织是不够的。 黄瓜标签 介绍对功能进行分类的标准方法 以及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_published1
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_published2
特性和场景标记与标准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_published3
测试设置
测试设置在给定的部分中实现。即使这些步骤 为了应用可能的副作用,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_published4
这还声明了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_published5
当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_published6
许多其他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_published7
功能测试可以重用为单元测试和升级创建的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_published8
这样,副作用就应用到了我们的文章中,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_published9
即使在使用正则表达式的步骤中,pytest bdd也会引发异常 获取参数的模式。
frompytest_bddimportscenario,given,when,then@scenario('publish_article.feature','Publishing the article')deftest_publish(browser):assertarticle.titleinbrowser.html0
如果步骤使用正则表达式模式,将引发异常。
frompytest_bddimportscenario,given,when,then@scenario('publish_article.feature','Publishing the article')deftest_publish(browser):assertarticle.titleinbrowser.html1
背景
通常情况下,要涵盖某些功能,需要多个场景。合乎逻辑的是 这些场景的设置将有一些公共部分(如果不相等)。为此,有背景。 Pytest BDD为 功能。
frompytest_bddimportscenario,given,when,then@scenario('publish_article.feature','Publishing the article')deftest_publish(browser):assertarticle.titleinbrowser.html2
在本例中,所有后台步骤都将在场景自身给定的所有步骤之前执行 步骤,添加在单个功能中为多个场景准备一些通用设置的可能性。 关于背景最佳实践,请阅读 此处
< div > 注"背景"部分只应使用"给定"步骤, 禁止使用"when"和"then"步骤,因为它们的目的是 与行动和消费结果相关,即 "背景"目标-准备测试系统或"放置系统 在一个已知的状态下"作为"给予它。 上面的语句适用于严格的gherkin模式,即 默认启用。