65.9K
CodeProject 正在变化。 阅读更多。
Home

SpecFlow 高级用法:使用 Hooks 扩展测试执行流程

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2016 年 10 月 30 日

Ms-PL

4分钟阅读

viewsIcon

30203

了解如何通过在工作流的各个点运行额外代码来扩展测试的执行工作流。配置 SpecFlow 挂钩的执行顺序。

引言

上周,我宣布了一个关于 Specflow(.NET 的行为驱动开发)的新系列文章。在我的第一篇出版物中,我向您展示了如何使用该框架创建简单的测试。今天的帖子将更高级,解释 SpecFlow 挂钩的概念。或者,如何在工作流的各个点运行额外代码来扩展测试的执行工作流。

什么是挂钩?

定义

The hooks (event bindings) can be used to perform additional automation logic on specific events, such 
as before executing a scenario. Hooks are global but can be restricted to run only for features or 
scenarios with a particular tag (see below). The execution order of hooks for the same event is 
undefined.

创建 SpecFlow 挂钩文件

  1. 向项目添加新项。

  2. 选择 SpecFlow 的挂钩模板。

以下类将自动生成。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TechTalk.SpecFlow;

namespace ExtendTestExecutionWorkflowUsingHooks
{
    [Binding]
    public sealed class Hooks
    {
        // For additional details on SpecFlow hooks see http://go.specflow.org/doc-hooks

        [BeforeScenario]
        public void BeforeScenario()
        {
            //TODO: implement logic that has to run before executing each scenario
        }

        [AfterScenario]
        public void AfterScenario()
        {
            //TODO: implement logic that has to run after executing each scenario
        }
    }
}

SpecFlow 挂钩的类型

SpecFlow 附带了一些预定义的挂钩,它们在测试执行期间的某些事件触发后执行。打个比方,可以将其与 MSTest 框架中的 TestInitializeTestCleanup 进行比较。

可用的挂钩及其运行顺序为

[BeforeTestRun]
[BeforeFeature]
[BeforeScenario]
[BeforeScenarioBlock]
[BeforeStep]
[AfterStep]
[AfterScenarioBlock]
[AfterScenario]
[AfterFeature]
[AfterTestRun]
Attribute 描述 是否静态 MSTest 对应

[BeforeTestRun]

[AfterTestRun]

在整个测试运行之前/之后运行

AssemblyInitializeAssemblyCleanup

[BeforeFeature]

[AfterFeature]

在执行每个功能之前/之后运行

ClassInitializeClassCleanup

[BeforeScenario][Before]

[AfterScenario][After]

在执行每个场景之前/之后运行

TestInitializeTestCleanup

[BeforeScenarioBlock]

[AfterScenarioBlock]

在执行每个场景块之前/之后运行(例如,“给定”和“何时”之间)

-

[BeforeStep]

[AfterStep]

在执行每个场景步骤之前/之后运行

-

测试中的 SpecFlow 挂钩

我将利用该系列第一篇文章中的测试示例,在该文章中我们构建了一个用于将千瓦时转换为牛顿·米(Newton Meters)的测试。第一个实现的缺点之一是我们需要在 SpecFlow 背景部分启动浏览器,并在单独的 Then 步骤中关闭它。

以前的功能文件

Feature: Convert Metrics for Nuclear Science
	To do my nuclear-related job
	As a Nuclear Engineer 
	I want to be able to convert different metrics.

Background:
    Given web browser is opened

@testingFramework
Scenario: Successfully Convert Kilowatt-hours to Newton-meters
	
	When I navigate to Metric Conversions
	And navigate to Energy and power section
	And navigate to Kilowatt-hours
	And choose conversions to Newton-meters
	And type 30 kWh
	Then assert that 1.080000e+8 Nm are displayed as answer
	Then close web browser

如上所述,我们需要在背景部分启动浏览器,并在 Then 步骤中关闭它。

不带挂钩的绑定类

[Binding]
public class ConvertMetricsForNuclearScienceSteps
{
    private HomePage homePage;
    private KilowattHoursPage kilowattHoursPage;

    [Given(@"web browser is opened")]
    public void GivenWebBrowserIsOpened()
    {
        Driver.StartBrowser(BrowserTypes.Chrome);
    }

    [Then(@"close web browser")]
    public void ThenCloseWebBrowser()
    {
        Driver.StopBrowser();
    }

    [When(@"I navigate to Metric Conversions")]
    public void WhenINavigateToMetricConversions_()
    {
        this.homePage = new HomePage(Driver.Browser);
        this.homePage.Open();
    }

    [When(@"navigate to Energy and power section")]
    public void WhenNavigateToEnergyAndPowerSection()
    {
        this.homePage.EnergyAndPowerAnchor.Click();
    }

    [When(@"navigate to Kilowatt-hours")]
    public void WhenNavigateToKilowatt_Hours()
    {
        this.homePage.KilowattHours.Click();
    }

    [When(@"choose conversions to Newton-meters")]
    public void WhenChooseConversionsToNewton_Meters()
    {
        this.kilowattHoursPage = new KilowattHoursPage(Driver.Browser);
        this.kilowattHoursPage.KilowatHoursToNewtonMetersAnchor.Click();
    }

    [When(@"type (.*) kWh")]
    public void WhenTypeKWh(double kWh)
    {
        this.kilowattHoursPage.ConvertKilowattHoursToNewtonMeters(kWh);
    }

    [Then(@"assert that (.*) Nm are displayed as answer")]
    public void ThenAssertThatENmAreDisplayedAsAnswer(string expectedNewtonMeters)
    {
        this.kilowattHoursPage.AssertFahrenheit(expectedNewtonMeters);
    }
}

在这里,我们具有用于启动和关闭浏览器的绑定方法。此外,每个页面都使用 new 关键字创建。

测试运行重用浏览器-挂钩类

[Binding]
public sealed class TestRunSingleBrowserHooks
{
    [BeforeTestRun]
    public static void RegisterPages()
    {
        Driver.StartBrowser(BrowserTypes.Chrome);
        UnityContainerFactory.GetContainer().RegisterType<HomePage>
                               (new ContainerControlledLifetimeManager());
        UnityContainerFactory.GetContainer().RegisterType<KilowattHoursPage>
                               (new ContainerControlledLifetimeManager());
        UnityContainerFactory.GetContainer().RegisterInstance<IWebDriver>(Driver.Browser);
    }

    // Reuse browser for the whole run.
    [AfterTestRun]
    public static void AfterTestRun()
    {
        Driver.StopBrowser();
    }
}

挂钩需要放在带有 Binding 属性标记的类中。在这里,我们将所有页面注册到 Unity IoC 容器 中,并在每次测试运行之前启动浏览器。这意味着浏览器将在所有测试(场景)之间重用。在 AfterTestRun 中,我们关闭浏览器。

测试场景重用浏览器-挂钩类

[Binding]
public sealed class TestScenarioBrowserHooks
{
    [BeforeTestRun]
    public static void RegisterPages()
    {
        UnityContainerFactory.GetContainer().RegisterType<HomePage>
                            (new ContainerControlledLifetimeManager());
        UnityContainerFactory.GetContainer().RegisterType<KilowattHoursPage>
                            (new ContainerControlledLifetimeManager());
    }

    [BeforeScenario
    public static void StartBrowser()
    {
        Driver.StartBrowser(BrowserTypes.Chrome);
        UnityContainerFactory.GetContainer().RegisterInstance<IWebDriver>(Driver.Browser);
    }

    [AfterScenario]
    public static void CloseBrowser()
    {
        Driver.StopBrowser();
    }       
}

如果我们将在 BeforeScenario 方法中放置启动浏览器的代码,则每个测试(场景)都会启动浏览器。此外,我们还需要在 AfterScenario 方法中关闭它。

带挂钩的功能文件

@firefox
Feature: Convert Metrics for Nuclear Science
	To do my nuclear-related job
	As a Nuclear Engineer 
	I want to be able to convert different metrics.

@hooksExample @firefox
Scenario: Successfully Convert Kilowatt-hours to Newton-meters
	
	When I navigate to Metric Conversions
	And navigate to Energy and power section
	And navigate to Kilowatt-hours
	And choose conversions to Newton-meters
	And type 30 kWh
	Then assert that 1.080000e+8 Nm are displayed as answer

新功能文件不包含任何与浏览器相关的代码。

带挂钩的绑定类

包含步骤绑定的类现在也不包含任何与浏览器相关的的方法。在构造函数中,我们从 Unity 容器中获取页面,而不是每次都使用 new 关键字创建它们。

[Binding]
public class ConvertMetricsForNuclearScienceSteps
{
    private readonly HomePage homePage;
    private readonly KilowattHoursPage kilowattHoursPage;

    public ConvertMetricsForNuclearScienceSteps()
    {
        this.homePage = 
            UnityContainerFactory.GetContainer().Resolve<HomePage>();
        this.kilowattHoursPage = 
            UnityContainerFactory.GetContainer().Resolve<KilowattHoursPage>();
    }

    ////[Given(@"web browser is opened")]
    ////public void GivenWebBrowserIsOpened()
    ////{
    ////    Driver.StartBrowser(BrowserTypes.Chrome);
    ////}

    ////[Then(@"close web browser")]
    ////public void ThenCloseWebBrowser()
    ////{
    ////    Driver.StopBrowser();
    ////}

    [When(@"I navigate to Metric Conversions")]
    public void WhenINavigateToMetricConversions_()
    {
        ////this.homePage = new HomePage(Driver.Browser);
        ////this.homePage = UnityContainerFactory.GetContainer().Resolve<HomePage>();
        this.homePage.Open();
    }

    [When(@"navigate to Energy and power section")]
    public void WhenNavigateToEnergyAndPowerSection()
    {
        this.homePage.EnergyAndPowerAnchor.Click();
    }

    [When(@"navigate to Kilowatt-hours")]
    public void WhenNavigateToKilowatt_Hours()
    {
        this.homePage.KilowattHours.Click();
    }

    [When(@"choose conversions to Newton-meters")]
    public void WhenChooseConversionsToNewton_Meters()
    {
        ////this.kilowattHoursPage = new KilowattHoursPage(Driver.Browser);
        this.kilowattHoursPage.KilowatHoursToNewtonMetersAnchor.Click();
    }

    [When(@"type (.*) kWh")]
    public void WhenTypeKWh(double kWh)
    {
        this.kilowattHoursPage.ConvertKilowattHoursToNewtonMeters(kWh);
    }

    [Then(@"assert that (.*) Nm are displayed as answer")]
    public void ThenAssertThatENmAreDisplayedAsAnswer(string expectedNewtonMeters)
    {
        this.kilowattHoursPage.AssertFahrenheit(expectedNewtonMeters);
    }
}

配置 SpecFlow 挂钩的执行顺序

SpecFlow 挂钩的另一个很酷的功能是,您可以为相同类型的多个挂钩指定执行顺序。默认情况下,执行顺序未指定,并且它们可以按任何顺序执行。为确保它们按指定顺序执行,挂钩属性允许配置任意顺序。值较低的顺序在值较高的顺序之前运行。在进行一些重构后,我们的挂钩文件将如下所示。

[Binding]
public sealed class Hooks
{
    // Reuse browser for the whole run.
    [BeforeTestRun(Order = 1)]
    public static void RegisterPages()
    {
        System.Console.WriteLine("Execute BeforeTestRun- RegisterPages");
        Driver.StartBrowser(BrowserTypes.Chrome);
        UnityContainerFactory.GetContainer().RegisterType<HomePage>
                              (new ContainerControlledLifetimeManager());
        UnityContainerFactory.GetContainer().RegisterType<KilowattHoursPage>
                              (new ContainerControlledLifetimeManager());
    }

    [BeforeTestRun(Order = 2)]
    public static void RegisterDriver()
    {
        System.Console.WriteLine("Execute BeforeTestRun- RegisterDriver");
        UnityContainerFactory.GetContainer().RegisterInstance<IWebDriver>(Driver.Browser);
    }

    // Reuse browser for the whole run.
    [AfterTestRun]
    public static void AfterTestRun()
    {
        System.Console.WriteLine("Execute AfterTestRun- StopBrowser");
        Driver.StopBrowser();
    }

    [BeforeFeature]
    public static void BeforeFeature()
    {
    }

    [AfterFeature]
    public static void AfterFeature()
    {
    }

    [BeforeScenario(Order = 2)]
    public static void StartBrowser()
    {

        // New Browser Instance for each test.
        ////Driver.StartBrowser(BrowserTypes.Chrome);
        System.Console.WriteLine("Execute BeforeScenario- StartBrowser");
    }

    [BeforeScenario(Order = 1)]
    public static void LoginUser()
    {
        System.Console.WriteLine("Execute BeforeScenario- LoginUser");
        // Login to your site.
    }

    [AfterScenario(Order = 2)]
    public static void CloseBrowser()
    {
        System.Console.WriteLine("Execute AfterScenario- CloseBrowser");
        // New Browser Instance for each test.
        ////Driver.StopBrowser();
    }

    [AfterScenario(Order = 1)]
    public static void LogoutUser()
    {
        System.Console.WriteLine("Execute AfterScenario- LogoutUser");
        // Logout the user
    }

    [BeforeStep]
    public void BeforeStep()
    {
        System.Console.WriteLine("BeforeStep- Start Timer");
    }

    [AfterStep]
    public static void AfterStep()
    {
        System.Console.WriteLine("BeforeStep- Log something in DB.");
    }
}

挂钩的标签范围

我们可以根据标签进行范围限定。标签添加到每个测试场景,以“@”符号开头。大多数挂钩支持标签范围限定,这意味着只有当功能或场景具有过滤器中指定的至少一个标签时,它们才会执行。

范围属性

您可以使用新的 Scope 属性来指定标签。

[AfterScenario(Order = 1)]
[Scope(Tag = "hooksExample")]
public static void LogoutUser()
{
    System.Console.WriteLine("Execute AfterScenario- LogoutUser");
    // Logout the user
}

步骤属性

此外,您可以在步骤属性构造函数中指定标签范围。

[AfterScenario(Order = 1)]
[AfterScenario("hooksExample")]
public static void LogoutUser()
{
    System.Console.WriteLine("Execute AfterScenario- LogoutUser");
    // Logout the user
}

高级标签范围限定

如果使用 ScenarioContext 类,则可以执行更高级的范围限定。在下面的示例中,如果未指定浏览器标签,我们将引发异常。

[BeforeScenario(Order = 2)]
public static void StartBrowser()
{
    // Advanced tag filtering
    if (!ScenarioContext.Current.ScenarioInfo.Tags.Contains("firefox"))
    {
        throw new ArgumentException("The browser is not specfied");
    }

    // New Browser Instance for each test.
    ////Driver.StartBrowser(BrowserTypes.Chrome);
    System.Console.WriteLine("Execute BeforeScenario- StartBrowser");
}

挂钩方法的执行顺序

[Binding]
public sealed class OrderHooks
{
    // Reuse browser for the whole run.
    [BeforeTestRun(Order = 1)]
    public static void RegisterPages()
    {
        System.Console.WriteLine("BeforeTestRun");
        Driver.StartBrowser(BrowserTypes.Chrome);
        UnityContainerFactory.GetContainer().RegisterType<HomePage>
                          (new ContainerControlledLifetimeManager());
        UnityContainerFactory.GetContainer().RegisterType<KilowattHoursPage>
                          (new ContainerControlledLifetimeManager());
    }

    [BeforeTestRun(Order = 2)]
    public static void RegisterDriver()
    {
        System.Console.WriteLine("Execute BeforeTestRun- RegisterDriver");
        UnityContainerFactory.GetContainer().RegisterInstance<IWebDriver>(Driver.Browser);
    }

    // Reuse browser for the whole run.
    [AfterTestRun]
    public static void AfterTestRun()
    {
        System.Console.WriteLine("AfterTestRun");
        Driver.StopBrowser();
    }

    [BeforeFeature]
    public static void BeforeFeature()
    {
        System.Console.WriteLine("BeforeFeature");
    }

    [AfterFeature]
    public static void AfterFeature()
    {
        System.Console.WriteLine("AfterFeature");
    }

    [BeforeScenario]
    public void LoginUser()
    {
        System.Console.WriteLine("BeforeScenario");
    }

    [AfterScenario(Order = 1)]
    public void AfterScenario()
    {
        System.Console.WriteLine("AfterScenario");
    }

    [BeforeStep]
    public void BeforeStep()
    {
        System.Console.WriteLine("BeforeStep");
    }

    [AfterStep]
    public void AfterStep()
    {
        System.Console.WriteLine("AfterStep");
    }
}

实际上,在测试之后执行了,我不确定为什么它没有显示在输出中。无论如何,它是最后执行的。

Specflow 系列

文章 高级 SpecFlow:使用挂钩扩展测试执行工作流 最初发布在 Automate The Planet

所有图片均从 DepositPhotos.com 购买,不可免费下载和使用。
许可协议

© . All rights reserved.