Как в SpecFlow я могу обмениваться данными между этапами / функциями?

У меня есть 2 функции, которые используют общий шаг «Когда», но разные шаги «Тогда» в разных классах.

Как мне получить доступ, например, к ActionResult из моего вызова контроллера MVC на шаге When в моих двух шагах Then?


person Simon Keep    schedule 21.05.2010    source источник


Ответы (6)


В SpecFlow 1.3 есть три метода:

  1. статические члены
  2. ScenarioContext
  3. ContextInjection

Комментарии:

  1. статические члены очень прагматичны и в этом случае не такие злые, как мы, как разработчики, могли бы сначала подумать (нет потоковой передачи или необходимости в насмешке / замене в определениях шагов)

  2. См. Ответ от @Si Keep в этой теме

  3. Если конструктору класса определения шага нужны аргументы, Specflow пытается внедрить эти аргументы. Это можно использовать для вставки одного и того же контекста в несколько определений шагов.

    См. Пример здесь: https://docs.specflow.org/projects/specflow/en/latest/Bindings/Context-Injection.html

person jbandi    schedule 03.06.2010
comment
я думаю, что также можно использовать переменные экземпляра, как в одном из их примеров: github.com/techtalk/SpecFlow-Examples/blob/master/BowlingKata/ - person Carl Hörberg; 14.07.2010
comment
@Carl: переменные экземпляра могут использоваться для обмена данными между определениями шагов, которые реализованы в одном классе. Но речь шла о пошаговых реализациях в разных классах. - person jbandi; 30.01.2012
comment
Преимущество ScenarioContext по сравнению со статическими членами состоит в том, что состояние затем может использоваться совместно с другими тестовыми классами, поэтому файлы in .feature можно свободно редактировать. На этой странице достаточно хорошо объясняются три метода: blog.markvincze. ru / how-to-store-state-during-specflow-tests - person andrew pate; 23.05.2017
comment
Просто взглянул на пример с использованием static, и он превратился в большой беспорядок, так что это все еще проблема. - person user1496062; 10.09.2019

Используйте класс ScenarioContext, который представляет собой словарь, общий для всех шагов.

ScenarioContext.Current.Add("ActionResult", actionResult);
var actionResult = (ActionResult) ScenarioContext.Current["ActionResult"];
person Simon Keep    schedule 21.05.2010
comment
Почему вы говорите, что это ужасный полковник? - person Turnkey; 27.11.2012
comment
Саймон выполнил правильную реализацию вопроса. Оператор теперь может выполнять рефакторинг по мере необходимости, вместо того, чтобы Саймон пытался угадать, как он этого хочет. mcintyre321 создал отличный вспомогательный метод ниже. - person Ralph Willgoss; 17.08.2013
comment
Обратите внимание, что ScenarioContext.Current теперь устарел - посмотрите мой ответ. - person Ory Zaidenvorm; 14.10.2020

У меня есть вспомогательный класс, который позволяет мне писать

Current<Page>.Value = pageObject;

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

 public static class Current<T> where T : class
 {
     internal static T Value 
     {
         get { 
               return ScenarioContext.Current.ContainsKey(typeof(T).FullName)
               ? ScenarioContext.Current[typeof(T).FullName] as T : null;
             }
         set { ScenarioContext.Current[typeof(T).FullName] = value; }
     }
 }

Редактировать 2019: в настоящее время я бы использовал ответ @ JoeT, похоже, вы получаете те же преимущества, не определяя расширение

person mcintyre321    schedule 20.12.2012

Мне не нравилось использовать Scenario.Context из-за необходимости отбрасывать каждую словарную статью. Я нашел другой способ сохранить и получить элемент без необходимости его использовать. Однако здесь есть компромисс, потому что вы эффективно используете тип в качестве ключа для доступа к объекту из словаря ScenarioContext. Это означает, что можно сохранить только один элемент этого типа.

TestPage testPageIn = new TestPage(_driver);
ScenarioContext.Current.Set<TestPage>(testPageIn);
var testPageOut = ScenarioContext.Current.Get<TestPage>();
person JoeT    schedule 06.07.2015
comment
Вы также можете сделать набор более лаконичным, например ScenarioContext.Current.Set (testPageIn); - person Howard; 17.07.2018
comment
Мне нравится, когда код показывает, что тип является «ключом» для хранения и извлечения объекта. Если мы сохраняем объект без указания типа, то менее очевидно, какой ключ нужен для извлечения. Однако определение этого типа должно быть довольно простым, глядя на окружающий код. Может потребоваться незначительное количество дополнительных умственных усилий, чтобы определить тип этого объекта, чтобы написать строку, которая его извлекает, по сравнению с тем, чтобы увидеть, что тип уже прописан в строке, которая его сохранила. - person JoeT; 21.07.2018

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

...
Then I remember the ticket number '<MyKey>'
....
When I type my ticket number '<MyKey>' into the search box
Then I should see my ticket number '<MyKey>' in the results 

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

person Daniel Kereama    schedule 15.01.2018

Поскольку это первый результат, который пришел мне в голову в Google, я просто подумал, что упомянул, что ответ @ jbandi является наиболее полным. Однако начиная с версии 3.0 и новее:

В SpecFlow 3.0 мы пометили ScenarioContext.Current и FeatureContext.Current как устаревшие, чтобы прояснить, что вам следует избегать использования этих свойств в будущем. Причина отказа от этих свойств заключается в том, что они не работают при параллельном запуске сценариев.

(ScenarioContext и FeatureContext в SpecFlow 3.0 и более поздних версиях)

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

Вы можете имитировать устаревший ScenarioContext.Current, внедрив экземпляр в свои классы привязки

[Binding]
public class MyStepDefs
{
 private readonly ScenarioContext _scenarioContext;

  public MyStepDefs(ScenarioContext scenarioContext) 
  { 
    _scenarioContext= scenarioContext ;
  }

  public SomeMethod()
  {
    _scenarioContext.Add("key", "value");

    var someObjectInstance = new SomeObject();
    _scenarioContext.Set<SomeObject>(someObjectInstance);
  
    _scenarioContext.Get<SomeObject>();
            
    // etc.
  }
}
person Ory Zaidenvorm    schedule 14.10.2020