Search code examples
c#winformsmvpspecflow

Using SpecFlow for project architecture (MVP-VM)


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

Solution

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