Можно ли создать повторяющиеся и общие определения шагов Specflow для нескольких похожих экранов, используя объекты страниц? - PullRequest
1 голос
/ 20 сентября 2019

Я работаю над приложением со многими похожими экранами данных / CRUD.Я использую Selenium с шаблоном объектов страницы для навигации в приложении, а матери объектов - для создания предопределенных тестовых данных, особенно для форм с множеством входных данных.

При написании файлов объектов мне пришло в голову, что тесты настолько похожи друг на друга и что должна быть возможность обобщить некоторые общие шаги ради повторного использования и СУХОГО состояния.Объект страницы DataTable был прост, поскольку селекторы одинаковы для всех страниц.Поэтому я создал DataTable объект страницы, который имеет необходимые операции для фильтрации, выбора строк и т. Д. Я использовал DI с помощью механизма ввода контекста Specflow.

Пример:

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();
    }
}

Проблема в том, что я не могу найти хороший дизайн, когда пытаюсь создать общий класс шагов 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();
    }
}

Одна вещь, которая пришла мне в голову, - это использовать интерфейсную область с крюком и зарегистрировать интерфейс с нужными объектами, однако в этом случае будет множество меток и методов подключения.Точно так же я могу зарегистрировать тип на каком-то другом шаге, например When I go to '/Users' step, но это усложнит ситуацию еще больше.Можно использовать отражение или указать имя типа в определении шага: 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

Ответы [ 2 ]

1 голос
/ 20 сентября 2019

Вы можете упростить все это, используя таблицу и объект передачи данных, представляющий значения из этой таблицы:

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>(), позволяющая указать лямбда-выражение, используемое для создания нового экземпляра 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"
1 голос
/ 20 сентября 2019

Возможно, наброски сценария помогут вам

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"  |
|.........|..........|
...