代码之家  ›  专栏  ›  技术社区  ›  Yamuk

是否可以使用页面对象为多个类似的屏幕创建可重用和通用的Specflow步骤定义?

  •  0
  • Yamuk  · 技术社区  · 5 年前

    我正在开发一个有许多类似datatable/CRUD屏幕的应用程序。我使用Selenium with page objects模式在应用程序中导航,使用object mothers创建预定义的测试数据,特别是对于具有许多输入的表单。

    在编写功能文件时,我突然想到这些测试彼此非常相似,为了重用和干燥,应该可以概括一些常见的步骤。DataTable页面对象很简单,因为所有页面的选择器都是相同的。所以我创造了一个 DataTable 具有筛选、选择行等必要操作的页对象。我通过Specflow的上下文注入机制使用了DI。

    Scenario: List and search Users
        When I type 'test@test.test' in filter
        Then I should only see items with 'email' = 'test@test.test'
    

    步骤:

    [Binding]
    public class DataTableSteps
    {
        private DataTable _page;
        public DataTableSteps(DataTable page) => _page = page;
    
        [When(@"I type '(.*)' in filter")]
        public void WhenITypeInFilter(string value) { 
            _page.FilterSearch (value);    
          }
        [Then(@"I should see items with '(.*)' = '(.*)'")]
        public void ThenIShouldSeeAListWhereContains(string column, string value)
        {
            _page.VisibleInTable(column, value).Should().BeTrue();
        }
    }
    

    问题是,当我试图创建一个普通的step类时,我找不到一个好的设计 WebFormSteps 处理表单数据以用不同的 WebForm 页面对象。我想做以下工作:

    Scenario: Add User
        When I go to '/Users'
        When I create a 'validUser' Item  #'validUser' comes from the object mother
        Then 'validUser' should be added
    

    并对产品使用相同的步骤和步骤定义,例如:

    Scenario: Add Product
        When I go to '/Products'
        When I create a 'validProduct' Item
        Then 'validProduct' should be added
    

    我想使用接口或抽象类 NewUserInputWebForm 页面对象和 UserMother 为了实现这些,但是我找不到在运行时注入正确具体类型的方法。(我考虑将具体的对象母对象放在相关的页面对象上,这意味着对步骤的依赖性降低了一点,但我无法证明将对象母对象添加到这样一个不相关的类(即页面对象)中是合理的)。

    [Binding]
    class WebFormSteps
    {
        IWebFormObject formObject;
        IObjectMother objectMother;
    
        public WebFormSteps(IWebFormObject formObject, IObjectMother objectMother)
        {
            this.formObject = formObject;
            this.objectMother = objectMother;
        }
    
        [When(@"I create a '([^']*)' item")]
        public void WhenICreate(string exampleName)
        {
            form = objectMother.Create(exampleName);
            formObject.FillForm(form);
            formObject.Submit();
        }
    }
    

    我想到的一件事是使用一个有作用域的hook和register接口来访问正确的对象,但是在这种情况下会有大量的标记和hook方法。类似地,我可以在其他步骤注册类型,例如 When I go to '/Users' 一步一步,但会让事情更复杂。可以使用反射,也可以在步骤定义中指定类型名称: When I create a 'User' named 'validUser' ,但这将使脆性测试变得更加脆弱。此外,测试将变得过于技术化,根本不是黄瓜。

    有什么好的方法可以达到这个目的吗?我甚至不确定这种方法是否是好的实践(包括数据表),因为我找不到更多关于这种用法的小例子。我是否应该坚持以下特殊步骤:

    Scenario: Add User
        When I go to '/Users'
        When I create a user named 'validUser'
        Then 'validUser' should be added to users
    
    0 回复  |  直到 5 年前
        1
  •  1
  •   Somber    5 年前

    可能情景大纲会帮助你

    Scenario Outline: Add User
        When I go to <Page>
        When I create a user named <Username>
        Then <Username> should be added to users
    Examples: 
    | Page    | Username |
    |"//Users"| "User1"  |
    |"//Users"| "User2"  |
    |"//Users"| "User3"  |
    |"//Smth" | "User1"  |
    |"//Smth" | "User2"  |
    |"//Smth" | "User3"  |
    |.........|..........|
    
        2
  •  1
  •   Greg Burghardt    5 年前

    When I create the following user:
        | Field      | Value |
        | Username   | test  |
        | First name | John  |
        | Last name  | Doe   |
    

    步骤定义将得到 Table CreateInstance<T>() 中的扩展方法 TechTalk.SpecFlow.Assist

    [When(@"I create the following user:")]
    public void WhenICreateTheFollowingUser(Table table)
    {
        var user = table.CreateInstance<UserRow>();
        var userForm = new AddEditUserPageObject(driver);
    
        // Send user object to Selenium page object in order to enter
        // data into form fields
        userForm.FillForm(user);
    }
    

    从表映射的类将是:

    public class UserRow
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Username { get; set; }
    }
    

    以及Selenium的示例页面对象:

    public class AddEditUserPageObject
    {
        private readonly IWebDriver driver;
    
        public AddEditUserPageObject(IWebDriver driver)
        {
            this.driver = driver;
        }
    
        public void FillForm(UserRow data)
        {
            Username.SendKeys(data.Username);
            FirstName.SendKeys(data.FirstName);
            LastName.SendKeys(data.LastName);
        }
    }
    

    如果您想利用对象母亲来设置一些默认参数,可以使用 table.CreateInstance<T>() 方法,该方法允许您指定一个lambda表达式,该表达式用于创建UserRow的新实例,然后可以使用对象母亲:

    [When(@"I create the following user:")]
    public void WhenICreateTheFollowingUser(Table table)
    {
        var user = table.CreateInstance<UserRow>(() => objectMother.CreateUser("someExample"));
        var userForm = new AddEditUserPageObject(driver);
    
        // Send user object to Selenium page object in order to enter
        // data into form fields
        userForm.FillForm(user);
    }
    

    如果要参数化传递给对象母亲的值,则始终可以添加新步骤:

    [When(@"I create a user named ""(.*)"":")]
    public void WhenICreateTheFollowingUser(string username, Table table)
    {
        var user = table.CreateInstance<UserRow>(() => objectMother.CreateUser(username));
        var userForm = new AddEditUserPageObject(driver);
    
        // Send user object to Selenium page object in order to enter
        // data into form fields
        userForm.FillForm(user);
    }
    

    功能文件中的步骤如下:

    When I create a user named "test"