I am trying SpecFlow for the first time, and I would like to know whether I am perhaps overthinking the whole concept or worse, completely misusing it for its intended purpose?
I thought to take an MVP-VM architectural design pattern for my WinForms project, and set out the boilerplate stories that would define future projects following the same pattern.
Any suggestions are welcome, thank you!
Feature: DesignPattern
In order to encourage pluggability and loose coupling
As a software developer who has to comply with company GUI standards
I want to make sure the MVP-VM design pattern is enforced
@mytag
Scenario: MainPresenter loosely couples with IMainView and IModelContainer implementations
Given a stub of the IMainView interface
And a stub of the IModelContainer interface
When I create a new MainPresenter with the IMainView and IModelContainer stubs as arguments
Then the MainPresenter should have the IMainView and IModelContainer stubs as properties
Scenario: MainPresenter tightly couples with MainViewModel
Given a stub of the IMainView interface
And a stub of the IModelContainer interface
When I create a new MainPresenter with the IMainView and IModelContainer stubs as arguments
Then the MainPresenter should have a collection of MainViewModels as a property
Scenario: IModelContainer contains all required model interfaces
Given a stub of the IModelContainer interface
Then the IModelContainer stub should have an IContractsModel property
Scenario: IMainView extends the company BaseView GUI standard
Given a stub of the IMainView interface
Then the IMainView stub should extend the IBaseView interface
Scenario: IMainView exposes a datasource binding method that accepts a collection of MainViewModels as argument
Given a stub of the IMainView interface
And a collection of MainViewModels
Then the IMainView stub should have a BindViewModelsList method that accepts the collection of MainViewModels
Scenario: MainViewModel takes a ContractDataEntity and stores it as a property
Given a ContractDataEntity
When I create a new MainViewModel with the ContractDataEntity as argument
Then the MainViewModel should have the ContractDataEntity as a property
Scenario: MainViewModel presents the required attributes of its associated DataEntity
Given a ContractDataEntity
When I create a new MainViewModel with the ContractDataEntity as argument
Then the MainViewModel should have the ContractDataEntity ContractNumber as a property
And the MainViewModel should have the ContractDataEntity CustomerCode as a property
Scenario: MainViewModel has a factory method that translates a collection of DataEntities into MainViewModels
Given a collection of ContractDataEntities
When I call the MainViewModel TranslateDataEntityList factory method
Then it should return a collection on MainViewModels
I would then generate classes, properties and method stubs from the SpecFlow methods:
using Rhino.Mocks;
using Should.Fluent;
using TechTalk.SpecFlow;
namespace CONTR001.Test
{
[Binding]
public class DesignPatternSteps
{
[Given(@"a stub of the IMainView interface")]
public void GivenAStubOfTheIMainViewInterface()
{
IMainView view = MockRepository.GenerateStub<IMainView>();
ScenarioContext.Current.Set(view);
}
[Given(@"a stub of the IModelContainer interface")]
public void GivenAStubOfTheIModelContainerInterface()
{
IModelContainer model = MockRepository.GenerateStub<IModelContainer>();
ScenarioContext.Current.Set(model);
}
...
[When(@"I create a new MainPresenter with the IMainView and IModelContainer stubs as arguments")]
public void WhenICreateANewMainPresenterWithTheIMainViewAndIModelContainerStubsAsArguments()
{
var view = ScenarioContext.Current.Get<IMainView>();
var model = ScenarioContext.Current.Get<IModelContainer>();
var presenter = new MainPresenter(view, model);
ScenarioContext.Current.Set(presenter);
}
...
[Then(@"the MainPresenter should have the IMainView and IModelContainer stubs as properties")]
public void ThenTheMainPresenterShouldHaveTheIMainViewAndIModelContainerStubsAsProperties()
{
var presenter = ScenarioContext.Current.Get<MainPresenter>();
presenter.View.Should().Equal(ScenarioContext.Current.Get<IMainView>());
presenter.Model.Should().Equal(ScenarioContext.Current.Get<IModelContainer>());
}
...
[Then(@"the IMainView stub should extend the IBaseView interface")]
public void ThenTheIMainViewStubShouldExtendTheIBaseViewInterface()
{
var view = ScenarioContext.Current.Get<IMainView>();
view.Should().Be.AssignableFrom<IBaseView>();
}
}
}
The fact that you have doubts about your overthinking/not using for intended purpose already tells you that have already learned greatly from this experience. SpecFlow is a tool that is primarily used to support BDD which is a process about getting knowledge from the business and defining criteria from success. This definitely isn't BDD, but I can certainly imagine that deriving these tests has enabled you to think through your domain in detail. You've generated some thorough examples of unit testing via specification, which is useful. You might choose to delete these tests in the future (not yet), once you think your code base is mature enough that they serve no purpose, and even so, writing them clarified your vision of how things should work.
When I first started using SpecFlow I wrote a large number of tests at a very similar level of granularity to these. I also wrote some more at a much higher level, almost system integration level. Now with hindsight, my opinion has changed and I find myself writing simliar low level tests in nUnit and my higher level tests in SpecFlow. Each technique simply has its place, and I find that the translation from Specflow into c# Bindings added an additional layer of complexity for low level tests.
Don't give up on SpecFlow/Gherkin/BDD though, it has some wonderful advantages over Unit testing when you use it at the right level of granularity. You simply need to find the level that you are happy with. For me, that's anywhere I can pull out a feature file and discuss it with somebody over a coffee.