测试架构设计评估系统 - 基于SpecFlow的测试





5.00/5 (3投票s)
我们将使用之前介绍的评估框架来评估基于 SpecFlow 的测试。了解评分和评分理由。
引言
在我之前的文章《测试架构设计评估系统》中,我向您介绍了用于系统测试架构设计的八项标准。为了全面理解该系统,我将使用它来评估几个实际案例,为每个标准分配评分,并解释我的理由。我们将要评估的第三种测试是基于 SpecFlow 的测试。如果您不熟悉 SpecFlow,可以查看我的SpecFlow 系列。
基于 SpecFlow 的测试
功能和场景
SpecFlow 使用 Gherkin DSL 来描述系统的行为,使用人类可读的语法。它使用所谓的“规范”,其中测试场景通过 Gherkin 语句进行描述。在构建时,DSL 被编译成 MSTest 测试。
Feature: Create Purchase in Amazon
In order to receive a book online
As a client
I want to be able to choose it through the browser and pay for it online
@testingFramework
Scenario: Create Successful Purchase
When Billing Country Is United States with American Express Card
When I navigate to "/Selenium-Testing-Cookbook-Gundecha-Unmesh/dp/1849515743"
And I click the 'buy now' button
And then I click the 'proceed to checkout' button
#When the login page loads
And I login with email = "g3984159@trbvm.com" and pass = "ASDFG_12345"
#When the shipping address page loads
And I type full name = "John Smith",
country = "United States", Adress = "950 Avenue of the Americas",
city = "New Your City", state = "New Your",
zip = "10001-2121" and phone = "00164644885569"
And I choose to fill different billing, full name = "John Smith",
country = "United States", Adress = "950 Avenue of the Americas",
city = "New Your City", state = "New Your",
zip = "10001-2121" and phone = "00164644885569"
And click shipping address page 'continue' button
And click shipping payment top 'continue' button
Then assert that order total price = "40.49"
绑定方法
您还需要为场景中使用的每个语句定义绑定方法。否则,测试将失败。这些绑定定义在标记了 Bindings 属性的标准 C# 类中。每个步骤方法都标记有步骤类型属性,其中包含该步骤的正则表达式模式。在步骤方法内部,调用页面逻辑。通过这种方式,每个步骤定义并执行测试工作流的一小部分。通过这种测试方法,不存在编写标准的 MSTest 类。
[Binding]
public class CreatePurchaseSteps
{
[When(@"I navigate to ""([^""]*)""")]
public void NavigateToItemUrl(string itemUrl)
{
var itemPage = UnityContainerFactory.GetContainer().Resolve<ItemPage>();
itemPage.Navigate(itemUrl);
}
[When(@"I click the 'buy now' button")]
public void ClickBuyNowButtonItemPage()
{
var itemPage = UnityContainerFactory.GetContainer().Resolve<ItemPage>();
itemPage.ClickBuyNowButton();
}
[When(@"then I click the 'proceed to checkout' button")]
public void ClickProceedToCheckoutButtonPreviewShoppingCartPage()
{
var previewShoppingCartPage =
UnityContainerFactory.GetContainer().Resolve<PreviewShoppingCartPage>();
previewShoppingCartPage.ClickProceedToCheckoutButton();
}
[When(@"the login page loads")]
public void SignInPageLoads()
{
var signInPage = UnityContainerFactory.GetContainer().Resolve<SignInPage>();
signInPage.WaitForPageToLoad();
}
[When(@"I login with email = ""([^""]*)"" and pass = ""([^""]*)""")]
public void LoginWithEmailAndPass(string email, string password)
{
var signInPage = UnityContainerFactory.GetContainer().Resolve<SignInPage>();
signInPage.Login(email, password);
}
[When(@"the shipping address page loads")]
public void ShippingPageLoads()
{
var shippingAddressPage =
UnityContainerFactory.GetContainer().Resolve<ShippingAddressPage>();
shippingAddressPage.WaitForPageToLoad();
}
[When(@"I type full name = ""([^""]*)"", country = ""([^""]*)"",
Adress = ""([^""]*)"",
city = ""([^""]*)"",
state = ""([^""]*)"",
zip = ""([^""]*)"" and
phone = ""([^""]*)""")]
public void FillShippingInfo(string fullName, string country,
string address, string state, string city, string zip, string phone)
{
var shippingAddressPage =
UnityContainerFactory.GetContainer().Resolve<ShippingAddressPage>();
var clientPurchaseInfo = new ClientPurchaseInfo(
new ClientAddressInfo()
{
FullName = fullName,
Country = country,
Address1 = address,
State = state,
City = city,
Zip = zip,
Phone = phone
});
shippingAddressPage.FillShippingInfo(clientPurchaseInfo);
}
[When(@"I choose to fill different billing, full name =
""([^""]*)"", country = ""([^""]*)"", Adress = ""([^""]*)"",
city = ""([^""]*)"", state = ""([^""]*)"", zip = ""([^""]*)"" and
phone = ""([^""]*)""")]
public void FillDifferentBillingInfo(string fullName,
string country, string address, string state, string city, string zip, string phone)
{
var shippingAddressPage =
UnityContainerFactory.GetContainer().Resolve<ShippingAddressPage>();
var shippingPaymentPage =
UnityContainerFactory.GetContainer().Resolve<ShippingPaymentPage>();
var clientPurchaseInfo = new ClientPurchaseInfo(
new ClientAddressInfo()
{
FullName = fullName,
Country = country,
Address1 = address,
State = state,
City = city,
Zip = zip,
Phone = phone
});
shippingAddressPage.ClickDifferentBillingCheckBox(clientPurchaseInfo);
shippingAddressPage.ClickContinueButton();
shippingPaymentPage.ClickBottomContinueButton();
shippingAddressPage.FillBillingInfo(clientPurchaseInfo);
}
[When(@"click shipping address page 'continue' button")]
public void ClickContinueButtonShippingAddressPage()
{
var shippingAddressPage =
UnityContainerFactory.GetContainer().Resolve<ShippingAddressPage>();
shippingAddressPage.ClickContinueButton();
}
[When(@"click shipping payment top 'continue' button")]
public void WhenClickTopPaymentButton()
{
var shippingPaymentPage = PerfectSystemTestsDesign.Base.UnityContainerFactory.
GetContainer().Resolve<ShippingPaymentPage>();
shippingPaymentPage.ClickTopContinueButton();
}
[Then(@"assert that order total price = ""([^""]*)""")]
public void AssertOrderTotalPrice(string itemPrice)
{
var placeOrderPage = PerfectSystemTestsDesign.Base.
UnityContainerFactory.GetContainer().Resolve<PlaceOrderPage>();
double totalPrice = double.Parse(itemPrice);
placeOrderPage.AssertOrderTotalPrice(totalPrice);
}
}
钩子方法
有所谓的“钩子类”,可以在运行、功能、代码块或步骤级别定义不同的预/后执行逻辑。例如,在这里,我们在每个测试之前启动一个浏览器,并将所有需要的页面注册为该测试的单例。之后,我们关闭浏览器。
[Binding]
public sealed class SpecflowHooks
{
[BeforeTestRun]
public static void BeforeTestRun()
{
Driver.StartBrowser(BrowserTypes.Chrome);
UnityContainerFactory.GetContainer().RegisterType
<ItemPage>(new ContainerControlledLifetimeManager());
UnityContainerFactory.GetContainer().RegisterType
<PreviewShoppingCartPage>(new ContainerControlledLifetimeManager());
UnityContainerFactory.GetContainer().RegisterType
<SignInPage>(new ContainerControlledLifetimeManager());
UnityContainerFactory.GetContainer().RegisterType
<ShippingAddressPage>(new ContainerControlledLifetimeManager());
UnityContainerFactory.GetContainer().RegisterType
<ShippingPaymentPage>(new ContainerControlledLifetimeManager());
UnityContainerFactory.GetContainer().RegisterType
<PlaceOrderPage>(new ContainerControlledLifetimeManager());
UnityContainerFactory.GetContainer().RegisterType
<ItemPageBuyBehaviour>(new ContainerControlledLifetimeManager());
UnityContainerFactory.GetContainer().RegisterType
<ItemPageNavigationBehaviour>(new ContainerControlledLifetimeManager());
UnityContainerFactory.GetContainer().RegisterType
<PlaceOrderPageAssertFinalAmountsBehaviour>(new ContainerControlledLifetimeManager());
UnityContainerFactory.GetContainer().RegisterType
<PreviewShoppingCartPageProceedBehaviour>(new ContainerControlledLifetimeManager());
UnityContainerFactory.GetContainer().RegisterType
<ShippingAddressPageContinueBehaviour>(new ContainerControlledLifetimeManager());
UnityContainerFactory.GetContainer().RegisterType
<ShippingAddressPageFillDifferentBillingBehaviour>(new ContainerControlledLifetimeManager());
UnityContainerFactory.GetContainer().RegisterType
<ShippingAddressPageFillShippingBehaviour>(new ContainerControlledLifetimeManager());
UnityContainerFactory.GetContainer().RegisterType
<ShippingPaymentPageContinueBehaviour>(new ContainerControlledLifetimeManager());
UnityContainerFactory.GetContainer().RegisterType
<SignInPageLoginBehaviour>(new ContainerControlledLifetimeManager());
UnityContainerFactory.GetContainer().RegisterType
<ShoppingCart>(new ContainerControlledLifetimeManager());
UnityContainerFactory.GetContainer().RegisterInstance
<IWebDriver>(PerfectSystemTestsDesign.Core.Driver.Browser);
}
[AfterTestRun]
public static void AfterTestRun()
{
Driver.StopBrowser();
}
[BeforeScenario]
public static void BeforeScenario()
{
}
[AfterScenario]
public static void AfterScenario()
{
}
}
数据驱动测试示例表
Specflow 通过数据表支持数据驱动测试 - 为表中的每一行生成一个新测试。请记住,我是手动格式化表格的。Visual Studio 对 Gherkin 的支持尚未达到所需水平。
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 |
将多个参数传递给步骤
此表将传递类似动态对象列表的内容给我们的绑定方法。顺便说一句,它实际上是使用动态 C# 对象实现的。
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 |
这是我们在测试中使用 SpecFlow 的参数表的方式。正如我所指出的,它会创建一个动态对象列表,我们可以遍历它们。
[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 的测试 - 评估系统
可维护性
关于行为的所有提及也适用于此方法。但是,由于存在额外的绑定文件,评分有所降低。此外,用户无法在功能文件中使用现有的标签和常量,这会导致硬编码数据和复制粘贴开发。
基于 Facade 的测试 | |
可维护性 | 3 |
可读性 | |
代码复杂度指数 | |
可用性 | |
灵活性 | |
学习曲线 | |
最少知识 |
可读性
SpecFlow 的主要优势在于可读性。所有步骤都通过 Gherkin DSL 以人类可读的语法进行描述。即使是基本的初始化方法也通过几句话进行描述,这使得它们对用户更有意义。
基于 Facade 的测试 | |
可维护性 | 3 |
可读性 | 5 |
代码复杂度指数 | |
可用性 | |
灵活性 | |
学习曲线 | |
最少知识 |
代码复杂度指数
这里的代码复杂度指数并不完全准确,因为 Visual Studio 不支持计算 Gherkin 文件的代码度量。绑定类的指数被标记为“非常好”。可能是因为没有基类。但是,它们依赖于多个类,如页面、行为或 Facade。我认为您会同意我,如果我们能够计算 Gherkin 文件的度量,由于上述原因,它们不会做得很好,因此我将整体评分降低了一分,仅标记为“好”。
基于 Facade 的测试 | |
可维护性 | 3 |
可读性 | 5 |
代码复杂度指数 | 3 |
可用性 | |
灵活性 | |
学习曲线 | |
最少知识 |
可用性
此参数的评分被评为“非常差”,因为在用户可以使用其测试中的任何步骤之前(假设他/她正在为新功能从头开始编写新测试),需要创建大量新类。SpecFlow 与 Visual Studio 的集成很差,编写时建议的步骤(IntelliSense)并不十分有用。如果您需要定义几个具有常见起始词的动作(您应该在绑定类中定义不同的重写方法 + 使用自定义正则表达式模式),那将是一个挑战。如果您使用示例表生成测试,则应手动格式化表格,如果您想使其可读。
基于 Facade 的测试 | |
可维护性 | 3 |
可读性 | 5 |
代码复杂度指数 | 3 |
可用性 | 1 |
灵活性 | |
学习曲线 | |
最少知识 |
灵活性
评分仅为“好”,因为为了让 SpecFlow 的 API 支持附加步骤,您需要在绑定类中创建包装方法,并使用自定义正则表达式。
基于 Facade 的测试 | |
可维护性 | 3 |
可读性 | 5 |
代码复杂度指数 | 3 |
可用性 | 1 |
灵活性 | 4 |
学习曲线 | |
最少知识 |
学习曲线
我猜写新测试会比只使用页面对象的方法更难,尤其是在没有使用 SpecFlow 句子步骤的现有测试的情况下。
基于 Facade 的测试 | |
可维护性 | 3 |
可读性 | 5 |
代码复杂度指数 | 3 |
可用性 | 1 |
灵活性 | 4 |
学习曲线 | 3 |
最少知识 |
最少知识原则
您只将必需的参数传递给具体的绑定方法。因此,评分被标记为“优秀”。
基于 Facade 的测试 | |
可维护性 | 3 |
可读性 | 5 |
代码复杂度指数 | 3 |
可用性 | 1 |
灵活性 | 4 |
学习曲线 | 3 |
最少知识 | 5 |
您可以观看我关于该系统的会议演讲,或下载完整的幻灯片。
设计与架构
- 创建混合测试框架 – Selenium 驱动控件
- 创建混合测试框架 – Selenium Driver 实现
- 创建混合测试自动化框架——接口契约
- 创建混合测试框架 – 测试框架驱动实现
- 创建混合测试框架 – 测试框架驱动控件
- 创建混合测试框架 – 高级元素查找扩展
- 创建混合测试框架 – 动态配置执行引擎
- 创建混合测试框架 - 抽象单元测试框架
文章《测试架构设计评估系统 - 基于 SpecFlow 的测试》首次出现在Automate The Planet。
所有图片均从 DepositPhotos.com 购买,不可免费下载和使用。