在设计 BDD Scenarios 时,有时会用到大量的测试数据,或是多个 Scenarios 共享这些大量数据,如果将这些数据都列在 Sceanrios 中,会使得 Scenario 非常庞大,大量重复的数据快也使得 Feature 文件非常庞大,数据行非常长等,导致可读性差,不够简洁。
这时我们就会思考能不能将数据放在某个文件中,Scenarios 中的参数可以跟这些外部数据关联起来。非常棒的是 SpecFlow 可以做到,支持加载外部数据源,并且非常方便地将这加载的数据导入到 Scenarios 中。
利用 SpecFlow ExternalData 插件,可以实现将测试数据和测试 Scenarios 分开,并且 Scenarios 之间可以重用这些测试数据。这个插件支持最低版本 SpecFlow 3.5.5 起。
Note: 标准的 RFC 4180 CSV 格式是带一行 header line,也就是表头 (ExternalData 插件利用 CsvHelper 来解析这类文件).
Note: XLSX 和 XLS 都是支持的 (ExternalData 插件利用 ExcelDataReader 来解析这类文件).
注意:
SpecFlow ExternalData 某些版本可能支持 JSON 格式的文件,可参考官网实例 ExternalDataSample,但是这个插件后面被重写了,最新版本不再支持 JSON
文件加载导入到 Scenarios 中,具体参考 Issue #2559
@DataSource:path-to-file
这个 tag 主要是指明数据来源,可以加到 Scenario 或 Scenario Outline 上。
注意
: 这个路径是基于 Feature 文件所在 folder 的相对路径。也就是说数据源文件应该放在 Feature 文件夹范围内,不能 Feature 文件夹范围外其它地方,不然编译会出错。
@DisableDataSource
@DataSource tag 可以加在 Feature 节点上,将所有 scenarios 转化成 scenario outlines。当整个 Feature 文件都用到相同的外部数据源时,这种方式非常有用。@DisableDataSource 用于少数 Scenarios 不使用这个 tag 在 Feature 节点的外部数据源。
@DataFormat:format
这个 tag 仅用在文件的扩展名不能被识别。
@DataSet:data-set-name
这个 tag 只应用于 Excel 文件。 默认是指定第一个 worksheet,当有多个 worksheet,就可以用这个 tag 指明特定的 worksheet。
@DataField:name-in-feature-file=name-in-source-file
这个 tag 用于 “重命名” 外部数据源的列名
tags 汇总:
都是最新版本:
SpecFlow 3.9.74
SpecRun.SpecFlow 3.9.31
SpecFlow.ExternalData 3.9.74
FluentAssertions 6.8.0
为了区分数据源 path,我们将准备两个数据源,一个直接放在 Feature 文件夹下,一个放在 Feature 文件夹下的子文件夹下。
Feature/TestData 目录下添加一个 CSV 文件 products.csv
Feature 目录下添加一个 Excel 文件 products.xlsx
Products.feature
Feature: Products@DataSource:TestData/products.csv
Scenario: The basket price is calculated correctly (csv)Given the price of is €And the customer has put 1 piece of in the basketWhen the basket price is calculatedThen the basket price should be €@DataSource:TestData/products.csv
Scenario Outline: Valid product prices are calculated (csv Outline) Given the price of is €And the customer has put 1 piece of in the basketWhen the basket price is calculatedThen the basket price should be €Examples: | product | price || Cheesecake | 2.0 |@DataSource:TestData/products.csv @DataField:product-name=product @DataField:price-in-EUR=price
Scenario: The basket price is calculated correctly (csv renamed fields) Given the price of is €And the customer has put 1 piece of in the basketWhen the basket price is calculatedThen the basket price should be €@DataSource:products.xlsx @DataSet:other_products
Scenario: The basket price is calculated correctly for other productsGiven the price of is €And the customer has put 1 piece of in the basketWhen the basket price is calculatedThen the basket price should be €
ProductBindings.cs
这里我们只是想知道 Scenarios 中的参数和测试文件的联系,所以只实现了部分 step,保证 Scenarios 能跑起来就 OK。
using System;
using TechTalk.SpecFlow;namespace ExternalData.Steps
{[Binding]public class ProductBindings{[Given(@"the price of (.*) is €(.*)")]public void GivenThePriceOfProductIsPrice(string product, float price){Console.WriteLine($"product:{product}");Console.WriteLine($"price:{price}");}[Given(@"the customer has put (.*) piece of (.*) in the basket")]public void GivenTheCustomerHasPutPieceOfProductInTheBasket(int number, string product){}[When(@"the basket price is calculated")]public void WhenTheBasketPriceIsCalculated(){}[Then(@"the basket price should be €(.*)")]public void ThenTheBasketPriceShouldBePrice(string price){}}
}
这个 Scenario 导入了 TestData/products.csv 文件的数据,注意文件路径
@DataSource:TestData/products.csv
@DataSource:TestData/products.csv
Scenario: The basket price is calculated correctly (csv)Given the price of is €And the customer has put 1 piece of in the basketWhen the basket price is calculatedThen the basket price should be €
products.csv 中有三行数据,所以 Scenario 会自动转换成 Scenario outline
执行其中一个 Scenario,Step 中的 参数和 CSV 列名对应上了,自动关联起来了。
Given the price of is €
[Given(@"the price of (.*) is €(.*)")]public void GivenThePriceOfProductIsPrice(string product, float price){Console.WriteLine($"product:{product}");Console.WriteLine($"price:{price}");}
@DataSource:TestData/products.csv
Scenario Outline: Valid product prices are calculated (csv Outline) Given the price of is €And the customer has put 1 piece of in the basketWhen the basket price is calculatedThen the basket price should be €Examples: | product | price || Cheesecake | 2.0 |
生成了 4 个 Scenarios,其中绿色部分是 Scenario 中 Example 中的数据,黄色高亮是 CSV 中三行数据生成的。
@DataField:product-name=product @DataField:price-in-EUR=price 通过这种方式将 Scenario 中的参数名和数据源中的列名映射起来。
@DataSource:TestData/products.csv @DataField:product-name=product @DataField:price-in-EUR=price
Scenario: The basket price is calculated correctly (csv renamed fields) Given the price of is €And the customer has put 1 piece of in the basketWhen the basket price is calculatedThen the basket price should be €
Excel 有三个 worksheet,其中 other_products 有两行测试数据。
@DataSet:other_products 通过 @DataSet 来指定特定的 worksheet
@DataSource:products.xlsx @DataSet:other_products
Scenario: The basket price is calculated correctly for other productsGiven the price of is €And the customer has put 1 piece of in the basketWhen the basket price is calculatedThen the basket price should be €
生成了 2 条 Scenarios,并且参数能够自动关联起来。