Можно ли разделить функции SpecFlow между шагами? - PullRequest
4 голосов
/ 21 февраля 2012

У нас есть код, который мы хотели бы проверить с трех разных точек зрения:

  • Внутренне (прямой звонок)
  • Веб-сервис
  • Веб-приложение

Поскольку мы не хотим писать файлы объектов три раза, кажется, что мы должны совместно использовать функции между шагами, но я не могу понять, как это можно сделать, кроме, возможно, совместного использования файлов между проектами VS, которые имеют всегда казалось немного, ну, неестественно.

Является ли обмен файлами объектов между проектами наилучшим способом достижения этого или существует более разумный метод?

Ответы [ 6 ]

5 голосов
/ 01 июля 2014

Я знаю, что об этом спрашивали довольно давно, но я думаю, что вы хотите, возможно, и, как я понимаю, есть несколько способов достичь этого. По сути, вам действительно нужно определить функцию один раз, но отключить шаги, которые вызываются для функции, в зависимости от того, вызываете ли вы внутреннюю службу, общедоступную веб-службу или WebApp. Обсуждаются различные подходы к решению этой проблемы в списке рассылки для specflow , но суть его заключается в следующем (пример демонстрирует подход API против пользовательского интерфейса, но то же самое относится к вашей внутренней или веб-службе против веб-приложение я считаю):

Вариант 1 #

(спасибо Оливеру Фридриху из списка рассылки)

вам нужно создать одну сборку для каждой опции, которую вы хотите изменить для шагов, одну для внутренней, одну для веб-службы и одну для веб-приложения. Вы говорите specflow обо всех различных сборках в конфигурации, например, так:

<specFlow>
  <stepAssemblies>
    <stepAssembly assembly="Tests.API" />
    <stepAssembly assembly="Tests.UI" />
  </stepAssemblies>
</specFlow>

тогда в ваших общих шагах тестирования у вас есть какой-то шаг [BeforeTestRun], который выбирает из какой сборки загружать шаги:

[Binding]
public class TestRunSetup {

    // this method needs to be static
    [BeforeTestRun]
    public static void BeforeTestRun() 
    {
        if (RunApiTests()) // <-- implement this method to choose whether to run the tests via your chosen method 
        {
            Assembly.Load("Tests.API");
        }
        else 
        {
            Assembly.Load("Tests.UI");
        }
    }
}

Вариант 2 ##

(спасибо Gáspár Nagy из списка рассылки)

Попробуйте сгенерировать тесты во время сборки . Я не уверен, как именно это будет работать, но это область, которую мы можем исследовать.

Вариант 3 ##

(спасибо Dan Mork из списка рассылки)

Если вы используете возможности внедрения зависимостей SpecFlow, вы можете создать интерфейс для выполнения вызовов, которые вы хотите сделать, и использовать это в общем наборе шагов. Затем вы можете иметь 3 реализации этого интерфейса: одну, которая вызывает вашу внутреннюю службу, другую, которая вызывает веб-службу, и другую, которая управляет веб-приложением. Осталось только вставить правильную реализацию этого интерфейса в файлы шагов specflow, что можно сделать так:

// the abstract concept of the system that could be implemented with 
Selenium, HttpClient, etc. 
public interface IDocument 
{ 
    string Title { get;} 
    void Load(); 
} 

// the steps that are executed when the scenarios from your feature 
file are executed 
[Binding] 
public class Steps 
{ 
    private readonly IDocument _document; 

    public Steps(IDocument document) 
    { 
        _document = document; 
    } 

    [Given("something")] 
    public void GivenSomething() 
    { 
        // set up given state 
    } 

    [When("I view the document")] 
    public void WhenIViewTheDocument() 
    { 
        _document.Load(); 
    } 

    [Then(@"the title should be ""(.*)""")] 
    public void Then(string title) 
    { 
        Assert.ArEqual(_document.Title, title); 
    } 
} 

// this is where the magic happens - get the dependency injection 
// container and register an IDocument implementation
[Binding] 
public class Dependencies 
{ 
    private readonly IObjectContainer _objectContainer; 

    public Dependencies(IObjectContainer objectContainer) 
    { 
        _objectContainer = objectContainer; 
    } 

    [BeforeScenario] 
    public void RegisterDocumentInterfaces() 
    { 
        // register the correct IDocument implementation - UI or API 
    } 
} 

Это все еще оставляет вас с проблемой, как узнать, какие Реализация для регистрации. Это будет зависеть от специфики вашего решение, среда сборки, среда выполнения теста, и т.д. Некоторые варианты для этого ...

  • параметр тега для BeforeScenarioAttribute
  • создать две разные конфигурации сборки для вашего проекта, которые каждый определяет разные константы и использует директивы прекомпилятора для встроить правильные регистрации в код
  • добавить условную логику для проверки переменной окружения
  • добавить условную логику для проверки настроек конфигурации
  • Я не проверял, но, возможно, ScopeAttribute расширяем для вам создать подкласс и предоставить свою собственную логику для или нет BeforeScenarioAttribute метод выполняется.

Надеюсь, это даст вам несколько вариантов делать то, что вы хотите.

ИМХО первый вариант, вероятно, лучший, хотя вариант 3 тоже довольно приятный.

2 голосов
/ 02 июля 2014

У нас был похожий случай, когда одни и те же функции должны были работать для разных методов ввода.Наше решение было похоже на вариант № 3 из ответа Сэма Холдера, но мы хотели использовать файлы конфигурации вместо кода, чтобы решить, какую реализацию использовать в каждом конкретном случае.

Этого можно достичь с помощью нашего собственного механизма DI.Помимо возможности регистрации реализаций во время выполнения, он также читает файл конфигурации при первом вызове и загружает описанные классы.

Теперь для каждого проекта, который совместно использует функции с другим, у нас есть файл .config, в котором мы указываем правильную реализацию для каждого общего интерфейса (в основном входные данные).

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

Если вы заинтересованы в решении, я выложу его на github.

1 голос
/ 01 апреля 2015

То, что сработало для нас, было чем-то похожим на вариант Сэма Холдера 1.

  1. Ссылка на обе зависимости в вашем проекте VS, так что обе dll будут скопированы в ваш каталог вывода (компиляции).
  2. используйте разные app.config файлы для разных сред.
  3. в каждом app.config настраивайте права stepassemblies (комментируйте / удаляйте тот, который не нужен)

    <specFlow>
      <stepAssemblies>
        <stepAssembly assembly="Tests.API" />
        <!--stepAssembly assembly="Tests.UI" /-->
      </stepAssemblies>
    </specFlow>
    

Мне не нужен ручной Assembly.Load вызов, потому что объявление stepAssembly передаст ссылку на NUnit в качестве привязки Assembly (см. здесь ).

Если у меня есть обе сборки, объявленные как stepAssemblies, я сталкиваюсь с исключением «неоднозначная ссылка».

Надеюсь, это поможет, потому что совместное использование функций очень полезно, если не нужно (когда вы обновляете свое программное обеспечение и продолжаете тестировать функциональный сценарий)например).

0 голосов
/ 22 февраля 2012

Шаги не привязаны к функции, если шаги одинаковы или, по крайней мере, некоторые из них, если формулировка такая же, или вы добавляете дополнительный атрибут к методу определения шага, тогда они могут быть повторно использованы в других сценариях. или особенности.

В зависимости от случая я бы написал их как дополнительные сценарии или функции.

0 голосов
/ 21 февраля 2012

Я не уверен, что думаю о том, что вы хотите правильно или нет, но это может быть то, что вы ищете:

Scenario Outline: Multiple approaches to test same code
    Given I am using <ApproachToCallCode>
    When I do something
    Then I expect this result

Scenarios: Approaches
    |ApproachToCallCode|
    |Internal          |
    |WebService        |
    |WebApp            |

Тогда, вы могли бы использовать условное выражение в данном методе? Я не уверен, что это лучший подход, но он должен работать.

0 голосов
/ 21 февраля 2012

Я думаю, что это действительно 3 разных теста, поэтому в моей памяти должно быть 3 набора файлов функций. Однако, если вы действительно не хотите идти по этому пути, вы можете указать что-то вроде таблицы примеров, например,

Scenario Outline: Testing app

Given I have performed a call to my service using <application>
When I do something
Then this happens

Examples:
|Application|
| web service |
| web application |
| direct call |

Приведенный выше сценарий будет выполняться 3 раза, передавая 3 значения. Значение будет передано в данный метод (в данном случае), чтобы вы могли использовать его для настройки некоторого контекста, а затем повторно использовать те же файлы определения шага. После этого каждый шаг будет знать, предполагается ли использовать веб-службу, веб-приложение или прямой вызов.

У меня все еще есть сомнения относительно этого, поскольку это отдельные тесты, но это способ сделать то, что вы хотите достичь.

...