Сильная связь между шагами в корнишоне - PullRequest
3 голосов
/ 05 января 2011

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

Посмотрите на этот шаг (украденный у Даррена ответ ниже и измененный):

Given a guy the following guy exists:  
| Name     | Age | Salary |  
| John Doe | 42  | 400    |  
When his salary changes to 420  
And I run the paycheck program  
Then he should be paid 420

Смотрите здесь, я начинаю с объекта Guy, а затем модифицирую объект - это то, что я тестирую.

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

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

Ответы [ 2 ]

6 голосов
/ 05 января 2011

Способ, которым я избегаю связывания и продвигаю возможность повторного использования, заключается в следующем:

1.) Группировать мои шаги по сущности, например AccountRepositorySteps (для AccountRepository) или AccountControllerSteps (для AccountController).

2.) Делайте шаги зависимыми от абстракций, а не от бетонов (так же, как мы делали бы с нашим рабочим кодом).

3.) Используйте текущий ScenarioContext для передачи значений между шагами и файлами шагов.

Вот краткий пример:

Given a guy with the name Darren exists
And a guy with the name John exists
When I hit the guy page
Then I should see two guys

RepositorySteps.cs

private List<string> guys;

[BeforeScenario]
public void Setup(){

   guys = new List<string>();

   var fake = new Mock<IRepository>();

   fake.Setup(x=>x.GetGuys()).Returns(guys);

   ScenarioContext.Current.Set(fake) // Mock<IRepository>
   ScenarioContext.Current.Set(fake.Object); // IRepository
}

[Given("a guy with the name '(.*)' exists"]
public void a(string guy){
   guys.Add(guy);

   // and if I need to pull out the mock, I can do it like so
   var fake = ScenarioContext.Current.Get<Mock<IRepository>>(); 
}

GuyController.cs

When["I hit the guy page"]
public void x(){
   var repository = ScenarioContext.Current.Get<IRepository>();
   var controller = new GuyController(repository);

   var result = controller.Index();
   ScenarioContext.Current.Set(result);
}

См. Здесь шаг дляGuyController получает этот фиктивный объект, но он не знает, что это фиктивный объект.Это просто IRepository для него.И если по какой-то причине вам необходимо загрузить REAL-репозиторий для IRepository и запустить свои спецификации, все, что вам нужно сделать, это загрузить ScenarioContext с реальным IRepository.

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

0 голосов
/ 27 сентября 2012

Интересно, вам лучше разделить поведение?

Scenario: Change Salary

Given a guy the following guy exists:  
| Name     | Age | Salary |  
| John Doe | 42  | 400    |  
When his salary changes to 420  
Then his salary should be 420

И ...

Scenario: Pay Guy

Given a guy the following guy exists:  
| Name     | Age | Salary |  
| John Doe | 42  | 400    |  
And I run the paycheck program  
Then he should be paid 400

Это отдельные единицы поведения.

Что касается общего контекста, самое лучшее решение, с которым я столкнулся, - это внедрение зависимостей. Создайте некоторый класс SharedContext и вставьте его в классы определения шага, которым нужен общий контекст. Таким образом, вы по-прежнему можете разделять файлы определений шагов так, как хотите, и они могут делиться контекстом. Многие инструменты поставляются с простой функциональностью контейнера IoC из коробки (например, SpecFlow).

class SharedContext
{
object MyObject1 {get; set;}
object MyObject2 {get; set;}
//Etc.
}

class StepDefinitions1
{

private SharedContext _context;

public Stepdefinitions1(SharedContext context)
{
this._context = context;
}    
//Now use this._context.Properties to get at the shared objects in your 
//step definitions    
}

Контейнер позаботится обо всем остальном.

Жизненный цикл объекта класса SharedContext - одиночный сценарий. То есть для каждого нового сценария создается новый SharedContext и передается через конструктор всем шагам в классах, которые ссылаются на него, до тех пор, пока не будет выполнен последний шаг «Тогда».

...