SpecFlow 高级用法:正确处理参数的 4 种方法






4.43/5 (3投票s)
了解如何处理需要多个参数的更复杂场景。使用方案大纲示例表创建数据驱动的测试。
↵
引言
在 Specflow 系列 的文章部分,您可以了解如何遵循 BDD 范例编写可被业务用户理解的验收测试。在这篇文章中,我将与您分享在功能文件中处理参数的四种方法。
1. 创建带有可选参数的步骤
用例
在 之前的示例 中,我们编写了将千瓦时转换为牛顿米的测试。输入 kWh 的步骤如下:“And type "30" kWh”。在转换器应用程序中,有一个新功能 - 更改答案的格式,我们需要对其进行测试。我们希望能够用一个参数来扩展之前的步骤,该参数用于选择格式 - “And type 30 kWh in Fractions format”。我们希望同时支持这两种步骤。
功能方案
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
Scenario: Successfully Convert Kilowatt-hours to Newton-meters in Fractions format
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 in Fractions format
Then assert that 1079999999⁄64 Nm are displayed as answer
测试几乎相同,只是“Type”步骤有细微差别。
绑定方法
[When(@"type (.*) kWh")]
public void WhenTypeKWh(double kWh)
{
this.kilowattHoursPage.ConvertKilowattHoursToNewtonMeters(kWh);
}
[When(@"type (.*) kWh in (.*) format")]
public void WhenTypeKWhInFormat(double kWh, Format format)
{
this.kilowattHoursPage.ConvertKilowattHoursToNewtonMeters(kWh, format);
}
为了同时支持这两种步骤,您需要创建两个独立的绑定方法。第一个方法接受一个参数,第二个方法接受两个参数。
public void ConvertKilowattHoursToNewtonMeters(
double kWh,
Format format = CelsiusFahrenheitPage.Format.Decimal)
{
this.CelsiusInput.SendKeys(kWh.ToString());
if (format != CelsiusFahrenheitPage.Format.Decimal)
{
string formatText =
Enum.GetName(typeof(CelsiusFahrenheitPage.Format), format);
new SelectElement(this.Format).SelectByText(formatText);
}
this.driverWait.Until(drv => this.Answer != null);
}
第一个绑定方法将调用不带可选参数的新方法。如果我们选择“Fractions”格式,该方法将从下拉列表中选择它并转换答案。
2. 通过 ArgumentTransformation 实现可选参数
用例
在这里,我们想将秒转换为分钟。但是,我们希望支持如下输入:
- 1 天,1 小时,1 分钟,1 秒
- 4 小时,3 分钟,2 秒
- 5 天,3 分钟
- 3 分钟,2 秒
- 4 小时
该步骤应将输入转换为秒,然后将其输出。
功能方案
场景将如下所示。如您所见,我们传递了不同的天、小时、分钟和秒的组合。
Scenario: Successfully Convert Seconds to Minutes
When I navigate to Seconds to Minutes Page
And type seconds for 1 day, 1 hour, 1 minute, 1 second
Then assert that 1501 minutes are displayed as answer
Scenario: Successfully Convert Seconds to Minutes No Minutes
When I navigate to Seconds to Minutes Page
And type seconds for 1 day, 1 hour, 1 second
Then assert that 1500 minutes are displayed as answer
绑定方法
正如您所见,“type seconds”绑定不包含关于这些输入的任何特殊内容。所有魔力都发生在 StepArgumentTransformation
步骤中,该步骤具有自定义的正则表达式模式。基本上,它只将日期时间输入的部分转换为 TimeSpan
,然后我们将其转换为秒。我将不详细解释正则表达式,因为这不是本文的主要主题。您可以在 官方 SpecFlow 文档 中找到更多信息。
[When(@"type seconds for (.*)")]
public void WhenTypeSeconds(TimeSpan seconds)
{
this.secondsToMinutesPage.ConvertSecondsToMintes(seconds.TotalSeconds);
}
[Then(@"assert that (.*) minutes are displayed as answer")]
public void ThenAssertThatSecondsAreDisplayedAsAnswer(int expectedMinutes)
{
this.secondsToMinutesPage.AssertMinutes(expectedMinutes.ToString());
}
[StepArgumentTransformation(@"(?:(\d*) day(?:s)?(?:, )?)?(?:(\d*) hour(?:s)?(?:, )?)?
(?:(\d*) minute(?:s)?(?:, )?)?(?:(\d*) second(?:s)?(?:, )?)?")]
public TimeSpan TimeSpanTransform(string days, string hours, string minutes, string seconds)
{
int daysParsed;
int hoursParsed;
int minutesParsed;
int secondsParsed;
int.TryParse(days, out daysParsed);
int.TryParse(hours, out hoursParsed);
int.TryParse(minutes, out minutesParsed);
int.TryParse(seconds, out secondsParsed);
return new TimeSpan(daysParsed, hoursParsed, minutesParsed, secondsParsed);
}
3. 数据驱动测试 - 示例表
用例
我们不想复制粘贴方案,而是想指定秒的输入和预期的结果,并为所有指定的数据运行测试。上面,我们为四个数据集生成了测试。
功能方案
我们可以使用场景大纲示例表来完成此用例。首先,我们不再使用“Scenario:”而是将其更改为“Scenario Outline:”在步骤下方,我们在“Examples”表中指定所有数据集。您可以通过“Examples:”行标记表的开始,然后表的第一个行包含参数名称。然后您可以使用以下语法 <yourparameter>
在场景中使用这些名称。
Scenario Outline: Successfully Convert Seconds to Minutes Table
When I navigate to Seconds to Minutes Page
And type seconds for <seconds>
Then assert that <minutes> minutes are displayed as answer
Examples:
| seconds | minutes |
| 1 day, 1 hour, 1 second | 1500 |
| 5 days, 3 minutes | 7203 |
| 4 hours | 240 |
| 180 seconds | 3 |
格式数据表
之后,您用“|”符号分隔数据。如果您手动输入分隔符,Visual Studio 会为您格式化表格,但如果您粘贴数据,则需要手动操作。
测试名称
对于每一行,Specflow 将生成一个单独的测试。请记住,名称基于表格的第一个参数(您可以在用例图片中看到)。因此,如果您的第一列数据不是唯一的,您的测试名称将使用数字生成。在这种情况下,您可以添加一个新的第一列,自己指定测试名称的后缀。
4. 将对象列表传递给步骤
用例
我们想在一个测试中将多个包含联盟代码的商品添加到购物车。我们需要将 URL 和联盟代码对的列表传递给我们的步骤。
功能方案
Scenario: Add Amazon Products with Affiliate Codes
When add products
| Url | AffilicateCode |
| /dp/B00TSUGXKE/ref=ods_gw_d_h1_tab_fd_c3 | affiliate3 |
| /dp/B00KC6I06S/ref=fs_ods_fs_tab_al | affiliate4 |
| /dp/B0189XYY0Q/ref=fs_ods_fs_tab_ts | affiliate5 |
| /dp/B018Y22C2Y/ref=fs_ods_fs_tab_fk | affiliate6 |
您可以使用上述语法传递参数表。测试将打开每个 URL,将指定的联盟代码作为查询参数添加到 URL,然后单击“Buy Now”按钮。
绑定方法
您需要安装 SpecFlow.Assist.Dynamic
NuGet 包并添加 using
语句到 TechTalk.SpecFlow.Assist
。您的步骤应该接受一个 Table
类型的参数。您可以在没有上述 NuGet 包的帮助下迭代表的项。但是,我认为这并不好。我认为这种方法更清晰。在传递表后,您使用 CreateDynamicSet
扩展方法,它返回一个动态对象集合。您可以像属性一样访问返回对象的表的不同列,但您需要确保没有拼写错误,因为在此模式下,Visual Studio 不会警告您。您可以在 此处 阅读更多关于 dynamic
类型的信息。
[When(@"add products")]
public void NavigateToItemUrl(Table productsTable)
{
var itemPage = UnityContainerFactory.GetContainer().Resolve<ItemPage>();
IEnumerable<dynamic> products = productsTable.CreateDynamicSet();
foreach (var product in products)
{
itemPage.Navigate(string.Concat(product.Url, "?", product.AffilicateCode));
itemPage.ClickBuyNowButton();
}
}
Specflow 系列
参考文献
文章 高级 SpecFlow:处理参数的 4 种方法 首次出现在 Automate The Planet。