1

我是第一次尝试 SpecFlow,我想知道我是否可能过度思考了整个概念,或者更糟糕的是,完全滥用它来达到预期目的?

我想为我的 WinForms 项目采用 MVP-VM 架构设计模式,并列出样板故事,以定义遵循相同模式的未来项目。

欢迎任何建议,谢谢!

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

然后,我将从 SpecFlow 方法生成类、属性和方法存根:

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

2 回答 2

2

您怀疑自己的过度思考/未用于预期目的这一事实已经告诉您,您已经从这次经历中学到了很多东西。SpecFlow 是一个主要用于支持 BDD 的工具,BDD 是一个从业务中获取知识并定义成功标准的过程。这绝对不是 BDD,但我当然可以想象得出这些测试使您能够详细考虑您的领域。您已经通过规范生成了一些完整的单元测试示例,这很有用。一旦您认为您的代码库足够成熟以至于它们没有任何用途,您可能会选择在将来(还没有)删除这些测试,即使如此,编写它们也阐明了您对事物应该如何工作的看法。

当我第一次开始使用 SpecFlow 时,我以与这些非常相似的粒度级别编写了大量测试。我还写了一些更高级别的文章,几乎是系统集成级别。现在事后看来,我的观点发生了变化,我发现自己在 nUnit 中编写了类似的低级测试,而在 SpecFlow 中编写了更高级别的测试。每种技术都有它的位置,我发现从 Specflow 到 c# Bindings 的转换为低级测试增加了一层额外的复杂性。

但是不要放弃 SpecFlow/Gherkin/BDD,当您在正确的粒度级别上使用它时,它比单元测试具有一些奇妙的优势。你只需要找到你满意的水平。对我来说,这就是我可以提取功能文件并与某人喝咖啡讨论的任何地方。

于 2013-03-04T13:48:59.053 回答
1

我不认为您将 SpecFlow 用于我所理解的原始目的,但是我认为您仍然“明白”并且您所做的仍然是一个很好的用途。

据我了解,SpecFlow 用于描述行为——我不会说这些场景就是这样的行为。但是,您已经设法用适当的语言描述了您的需求并为它们编写了测试,所以我想这证明它在这方面也很有用。祝你好运!

于 2013-03-04T13:00:34.093 回答